MLIR 23.0.0git
XeGPUOps.cpp
Go to the documentation of this file.
1//===- XeGPUOps.cpp - MLIR XeGPU ops implementation -------------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
13#include "mlir/IR/Builders.h"
16
17#include "llvm/Support/Debug.h"
18
19#define DEBUG_TYPE "xegpu"
20
21using namespace mlir;
22using namespace mlir::xegpu;
23
24template <typename T>
25static std::string makeString(T array, bool breakline = false) {
26 std::string buf;
27 buf.clear();
28 llvm::raw_string_ostream os(buf);
29 os << "[";
30 for (size_t i = 1; i < array.size(); i++) {
31 os << array[i - 1] << ", ";
32 if (breakline)
33 os << "\n\t\t";
34 }
35 os << array.back() << "]";
36 return buf;
37}
38
41 if (auto ty = llvm::dyn_cast<ShapedType>(type))
42 shape = SmallVector<int64_t>(ty.getShape());
43 else
44 shape.push_back(1);
45 return shape;
46}
47
48static bool isReadHintOrNone(const CachePolicyAttr &attr) {
49 if (!attr)
50 return true;
51 auto kind = attr.getValue();
52 return kind == CachePolicy::CACHED || kind == CachePolicy::UNCACHED ||
53 kind == CachePolicy::STREAMING || kind == CachePolicy::READ_INVALIDATE;
54}
55
56static bool isWriteHintOrNone(const CachePolicyAttr &attr) {
57 if (!attr)
58 return true;
59 auto kind = attr.getValue();
60 return kind == CachePolicy::CACHED || kind == CachePolicy::UNCACHED ||
61 kind == CachePolicy::WRITE_BACK || kind == CachePolicy::WRITE_THROUGH;
62}
63
64static LogicalResult
66 VectorType valueTy, int64_t chunkSize,
68
69 auto maskVecTy = dyn_cast<VectorType>(maskTy);
70 auto offsetsVecTy = dyn_cast<VectorType>(offsetsTy);
71 if (!valueTy) {
72 if (chunkSize > 1)
73 return emitError() << "Expecting chunk size == 1 for scalar result";
74 if (maskVecTy || offsetsVecTy)
75 return emitError() << "Expecting scalar mask and offsets.";
76 else if (maskVecTy && offsetsVecTy)
77 return emitError() << "Expecting a vector type result.";
78 return success();
79 }
80
81 auto valueSize = valueTy.getNumElements();
82 // SIMT mode with scalar mask and offsets.
83 if (!maskVecTy && !offsetsVecTy) {
84 if (valueSize != chunkSize)
85 return emitError() << "value elements must match chunk size "
86 << chunkSize;
87 return success();
88 }
89 auto maskShape = getShapeOf(maskTy);
90 auto valueShape = getShapeOf(valueTy);
91
92 if (!maskVecTy)
93 return emitError() << "Expecting a vector type mask.";
94 int64_t maskSize = maskVecTy.getNumElements();
95
96 if (chunkSize > 1) {
97 if ((valueTy.getRank() == 1) && (valueSize != chunkSize))
98 return emitError() << "value elements must match chunk size "
99 << chunkSize;
100 } else {
101 if (valueSize != maskSize)
102 return emitError()
103 << "Mask should match value except the chunk size dim.";
104 }
105 llvm::SmallVector<int64_t> expectedMaskShape(valueShape);
106 if (maskSize == 1)
107 return success();
108 if (chunkSize > 1)
109 expectedMaskShape.pop_back();
110 if (expectedMaskShape != maskShape)
111 return emitError() << "Mask should match value except the chunk size dim.";
112
113 return success();
114}
115
116// Validates the `contiguity` attribute against the op's offsets type: the
117// innermost offsets dimension is contiguous in runs of `size`, so `size` must
118// be >= 2 and must divide that dimension.
119static LogicalResult
120isValidContiguity(std::optional<uint64_t> contiguity, Type offsetsTy,
122 if (!contiguity)
123 return success();
124 auto offsetsVecTy = dyn_cast<VectorType>(offsetsTy);
125 if (!offsetsVecTy)
126 return emitError() << "contiguity requires vector offsets (one per lane).";
127 int64_t size = static_cast<int64_t>(*contiguity);
128 int64_t inner = offsetsVecTy.getShape().back();
129 if (size < 2)
130 return emitError() << "contiguity = " << size << " (must be >= 2)";
131 if (inner % size != 0)
132 return emitError() << "contiguity = " << size
133 << " (must divide the innermost offsets dim " << inner
134 << ")";
135 return success();
136}
137
138LogicalResult
139IsValidMatrixOpParams(VectorType dataTy, MemDescType mdescTy,
140 UnitAttr subgroup_block_io, DistributeLayoutAttr layout,
142
143 if (!dataTy) {
144 if (subgroup_block_io)
145 return emitError() << "subgroup_block_io "
146 "are only allowed when result is a VectorType.";
147 else
148 return success();
149 }
150
151 ArrayRef<int64_t> dataShape = dataTy.getShape();
152 ArrayRef<int64_t> mdescShape = mdescTy.getShape();
153
154 SmallVector<int64_t> blockShape = mdescTy.getBlockShape();
155 ArrayAttr strideAttr = mdescTy.getStrideAttr();
156 SmallVector<int64_t> strides;
157 for (Attribute attr : strideAttr.getValue()) {
158 strides.push_back(cast<IntegerAttr>(attr).getInt());
159 }
160 if (subgroup_block_io && layout) {
161 auto laneData = layout.getEffectiveLaneDataAsInt();
162 auto laneLayout = layout.getEffectiveLaneLayoutAsInt();
163 if (!laneData.empty()) {
164 bool isLaneDataContiguous =
165 std::all_of(laneData.begin(), std::prev(laneData.end()),
166 [](int x) { return x == 1; });
167 if (!isLaneDataContiguous)
168 return emitError() << "With subgroup_block_io, accessed data must be "
169 "contiguous and coalesced.";
170 for (size_t i = 0; i < laneData.size(); ++i) {
171 if (laneLayout[i] != blockShape[i])
172 return emitError() << "With subgroup_block_io, the block shape must "
173 "match the lane layout.";
174 if (laneLayout[i] != 1 && strides[i] != 1)
175 return emitError() << "With subgroup_block_io, the distributed "
176 "dimensions must be contiguous.";
177 }
178 }
179 }
180
181 if (layout && !layout.isDistributable(
182 SmallVector<int64_t>(dataShape.begin(), dataShape.end())))
183 return emitError() << "Value shape is not distributable with the layout";
184
185 if (dataShape.size() == mdescShape.size()) {
186 if (llvm::any_of(llvm::zip_equal(dataShape, mdescShape),
187 [](auto p) { return std::get<0>(p) > std::get<1>(p); }))
188 return emitError() << "data shape must not exceed mem_desc shape.";
189 }
190 // if the subgroup_block_io attribute is set, mdescTy must have block
191 // attribute
192 if (subgroup_block_io && !blockShape.size())
193 return emitError() << "mem_desc must have block attribute when "
194 "subgroup_block_io is set.";
195 return success();
196}
197
198//===----------------------------------------------------------------------===//
199// XeGPU_CreateNdDescOp
200//===----------------------------------------------------------------------===//
201
202void CreateNdDescOp::build(OpBuilder &builder, OperationState &state,
203 Type tdesc, TypedValue<MemRefType> source) {
204 [[maybe_unused]] auto ty = source.getType();
205 assert(ty.hasStaticShape() && "expecting a memref with static shape");
206
207 build(builder, state, tdesc, source, ValueRange({}) /* empty dynamic shape */,
208 ValueRange({}) /* empty dynamic strides */,
209 DenseI64ArrayAttr({}) /* empty const shape*/,
210 DenseI64ArrayAttr({}) /* empty const strides*/);
211}
212
213void CreateNdDescOp::build(OpBuilder &builder, OperationState &state,
214 Type tdesc, Value source,
217 Type srcTy = source.getType();
218 assert((isa<IntegerType, MemRefType>(srcTy)) &&
219 "Source has to be either int or memref.");
220
221 llvm::SmallVector<Value> dynamicShape;
222 llvm::SmallVector<Value> dynamicStrides;
223
224 llvm::SmallVector<int64_t> staticShape;
225 llvm::SmallVector<int64_t> staticStrides;
226
227 dispatchIndexOpFoldResults(shape, dynamicShape, staticShape);
228 dispatchIndexOpFoldResults(strides, dynamicStrides, staticStrides);
229
230 auto staticShapeAttr = builder.getDenseI64ArrayAttr(staticShape);
231 auto staticStridesAttr = builder.getDenseI64ArrayAttr(staticStrides);
232
233 if (auto memrefTy = dyn_cast<MemRefType>(srcTy)) {
234 auto memrefShape = memrefTy.getShape();
235 auto [memrefStrides, _] = memrefTy.getStridesAndOffset();
236
237 // if shape and strides are from Memref, we don't need attributes for them
238 // to keep the IR print clean (only do so for full-static case, otherwise
239 // printer would fail trying to print empty array-attr).
240 if (staticShape == memrefShape && staticStrides == memrefStrides &&
241 dynamicShape.empty() && dynamicStrides.empty()) {
242 staticShapeAttr = DenseI64ArrayAttr();
243 staticStridesAttr = DenseI64ArrayAttr();
244 }
245 }
246
247 build(builder, state, tdesc, source, dynamicShape, dynamicStrides,
248 staticShapeAttr, staticStridesAttr);
249}
250
251LogicalResult CreateNdDescOp::verify() {
252 size_t rank = getMixedSizes().size();
253 bool invalidRank = rank != getMixedStrides().size();
254 bool invalidElemTy = false;
255
256 // Memory space of created TensorDesc should match with the source.
257 // Both source and TensorDesc are considered for global memory by default,
258 // if the memory scope attr is not specified. If source is an integer,
259 // it is considered as ptr to global memory.
260 auto srcMemorySpace = getSourceMemorySpace();
261 auto tdescMemorySpace = static_cast<unsigned>(getType().getMemorySpace());
262 if (srcMemorySpace != tdescMemorySpace)
263 return emitOpError("Memory space mismatch.")
264 << " Source: " << srcMemorySpace
265 << ", TensorDesc: " << tdescMemorySpace;
266
267 // check source type matches the rank if it is a memref.
268 // It also should have the same ElementType as TensorDesc.
269 if (auto memrefTy = dyn_cast<MemRefType>(getSourceType()))
270 invalidElemTy |= memrefTy.getElementType() != getElementType();
271
272 if (llvm::isa<IntegerType>(getSourceType())) {
273 // strides and shape must present for integer source.
274 if (getMixedStrides().empty() || getMixedSizes().empty())
275 return emitOpError("expecting strides and shape to be present for "
276 "integer source.");
277 }
278
279 if (invalidRank)
280 return emitOpError(
281 "Expecting the rank of shape, strides, and source (if source "
282 "is a memref) should match with each other.");
283
284 // check result TensorDesc rank
285 if (getType().getRank() > (int64_t)rank)
286 return emitOpError("Expecting the TensorDesc rank is not greater than the "
287 "ranks of shape, strides or the memref source.");
288
289 if (invalidElemTy)
290 return emitOpError("TensorDesc should have the same element "
291 "type with the source if it is a memref.\n");
292
293 return success();
294}
295
296//===----------------------------------------------------------------------===//
297// XeGPU_PrefetchNdOp
298//===----------------------------------------------------------------------===//
299
300void PrefetchNdOp::build(OpBuilder &builder, OperationState &state,
301 Value tensorDesc, ArrayRef<OpFoldResult> offsets,
302 xegpu::CachePolicyAttr l1_hint,
303 xegpu::CachePolicyAttr l2_hint,
304 xegpu::CachePolicyAttr l3_hint,
305 xegpu::DistributeLayoutAttr layout) {
306 SmallVector<Value> dynamicOffsets;
307 SmallVector<int64_t> staticOffsets;
308 dispatchIndexOpFoldResults(offsets, dynamicOffsets, staticOffsets);
309
310 auto staticOffsetsAttr = builder.getDenseI64ArrayAttr(staticOffsets);
311
312 build(builder, state, tensorDesc, dynamicOffsets, staticOffsetsAttr, l1_hint,
313 l2_hint, l3_hint, /*anchor_layout=*/layout);
314}
315
316LogicalResult PrefetchNdOp::verify() {
317 auto tdescTy = getTensorDescType();
318
319 if (!isReadHintOrNone(getL1HintAttr()))
320 return emitOpError("invalid l1_hint: ") << getL1HintAttr();
321
322 if (!isReadHintOrNone(getL2HintAttr()))
323 return emitOpError("invalid l2_hint: ") << getL2HintAttr();
324
325 if (!isReadHintOrNone(getL3HintAttr()))
326 return emitOpError("invalid l3_hint: ") << getL3HintAttr();
327
328 int64_t tDescRank = tdescTy.getRank();
329 int64_t offsetSize = getMixedOffsets().size();
330 if (offsetSize != tDescRank)
331 return emitOpError(
332 "Mismatched ranks between offsets and tensor descriptor");
333
334 if (auto layout = getAnchorLayout()) {
335 if (!layout.isDistributable(getShapeOf(tdescTy)))
336 return emitOpError(
337 "TensorDesc shape is not distributable with the layout");
338 }
339
340 return success();
341}
342
343//===----------------------------------------------------------------------===//
344// XeGPU_LoadNdOp
345//===----------------------------------------------------------------------===//
346
347void LoadNdOp::build(OpBuilder &builder, OperationState &state, Type retType,
348 Value tensorDesc, ArrayRef<OpFoldResult> offsets,
349 UnitAttr packed, DenseI64ArrayAttr transpose,
350 xegpu::CachePolicyAttr l1_hint,
351 xegpu::CachePolicyAttr l2_hint,
352 xegpu::CachePolicyAttr l3_hint,
353 xegpu::DistributeLayoutAttr layout) {
354 SmallVector<Value> dynamicOffsets;
355 SmallVector<int64_t> staticOffsets;
356 dispatchIndexOpFoldResults(offsets, dynamicOffsets, staticOffsets);
357
358 auto staticOffsetsAttr = builder.getDenseI64ArrayAttr(staticOffsets);
359
360 build(builder, state, retType, tensorDesc, dynamicOffsets, staticOffsetsAttr,
361 packed, transpose, l1_hint, l2_hint, l3_hint,
362 /*anchor_layout=*/layout);
363}
364
365LogicalResult LoadNdOp::verify() {
366 auto tdescTy = getTensorDescType();
367 auto valueTy = getType();
368
369 if (!valueTy)
370 return emitOpError("Invalid result, it should be a VectorType.\n");
371
372 if (!isReadHintOrNone(getL1HintAttr()))
373 return emitOpError("invalid l1_hint: ") << getL1HintAttr();
374
375 if (!isReadHintOrNone(getL2HintAttr()))
376 return emitOpError("invalid l2_hint: ") << getL2HintAttr();
377
378 if (!isReadHintOrNone(getL3HintAttr()))
379 return emitOpError("invalid l3_hint: ") << getL3HintAttr();
380
381 int tdescElems = tdescTy.getNumElements() * tdescTy.getArrayLength();
382 int valueElems = valueTy.getNumElements();
383
384 // If the result vector is 1D and has less elements than the tensor
385 // descriptor, it is supposed to be a SIMT op. The layout attribute in
386 // tensor_desc is not needed.
387 if (valueElems < tdescElems && valueTy.getRank() == 1) {
388 // SIMT mode doesn't need LayoutAttr.
389 if (tdescTy.getLayoutAttr())
390 return emitOpError()
391 << "TensorDesc doesn't need LayoutAttr for SIMT code";
392
393 // For SIMT code, the load is evenly distributed across all lanes in a
394 // subgroup. Since subgroup size is arch dependent, we only check even
395 // distribution here.
396 if (tdescElems % valueElems)
397 return emitOpError()
398 << "Result shape " << makeString(getShapeOf(valueTy))
399 << " is not a valid distribution for tensor descriptor "
400 << tdescTy;
401
402 return success();
403 }
404
405 // Check SIMD mode.
406 auto tdescShape = getShapeOf(tdescTy);
407 auto valueShape = getShapeOf(valueTy);
408
409 if (getTranspose()) {
410 auto trans = getTranspose().value();
411 // Make sure the transpose value is valid, and apply it
412 if (llvm::all_of(trans, [&](size_t s) { return s < tdescShape.size(); }))
413 tdescShape = applyPermutation(tdescShape, trans);
414 else
415 mlir::emitWarning(getLoc()) << "Invalid transpose attr. It is ignored.";
416 }
417
418 if (getPacked()) {
419 if (tdescTy.getRank() == 2) {
420 const int axis = 0;
421 auto vnni_factor = valueShape.back();
422 tdescShape[axis] /= vnni_factor;
423 tdescShape.push_back(vnni_factor);
424 } else {
425 mlir::emitWarning(getLoc())
426 << "Invalid Packed Attr. It is ignored (available for 2D "
427 "TensorDesc only).";
428 }
429 }
430
431 // Handle array_length. Two result shape conventions are accepted:
432 // * 3D shape: leading array_length dimension prepended, e.g. descriptor
433 // 16x16 with array_length=2 -> [2, 16, 16].
434 // * Stacked 2D shape: array blocks stacked along the non-FCD (first)
435 // dimension, e.g. descriptor 16x16 with array_length=2 -> [32, 16].
436 auto array_len = tdescTy.getArrayLength();
437 SmallVector<int64_t> stacked2DShape(tdescShape);
438 SmallVector<int64_t> threeDShape(tdescShape);
439 if (array_len > 1 && !tdescShape.empty()) {
440 stacked2DShape[0] *= array_len;
441 threeDShape.insert(threeDShape.begin(), array_len);
442 }
443
444 if (valueShape != stacked2DShape && valueShape != threeDShape)
445 return emitOpError() << "Result shape " << makeString(valueShape)
446 << " is not consistent with tensor descriptor "
447 << tdescTy;
448
449 int64_t tDescRank = tdescTy.getRank();
450 int64_t offsetSize = getMixedOffsets().size();
451 if (offsetSize != tDescRank)
452 return emitOpError(
453 "Mismatched ranks between offsets and tensor descriptor");
454
455 if (auto layout = getAnchorLayout()) {
456 if (!layout.isDistributable(getShapeOf(tdescTy)))
457 return emitOpError(
458 "TensorDesc shape is not distributable with the layout");
459 }
460
461 return success();
462}
463
464//===----------------------------------------------------------------------===//
465// XeGPU_StoreNdOp
466//===----------------------------------------------------------------------===//
467
468void StoreNdOp::build(OpBuilder &builder, OperationState &state, Value value,
469 Value tensorDesc, ArrayRef<OpFoldResult> offsets,
470 xegpu::CachePolicyAttr l1_hint,
471 xegpu::CachePolicyAttr l2_hint,
472 xegpu::CachePolicyAttr l3_hint,
473 xegpu::DistributeLayoutAttr layout) {
474 SmallVector<Value> dynamicOffsets;
475 SmallVector<int64_t> staticOffsets;
476 dispatchIndexOpFoldResults(offsets, dynamicOffsets, staticOffsets);
477
478 auto staticOffsetsAttr = builder.getDenseI64ArrayAttr(staticOffsets);
479
480 build(builder, state, value, tensorDesc, dynamicOffsets, staticOffsetsAttr,
481 l1_hint, l2_hint, l3_hint, /*anchor_layout=*/layout);
482}
483
484LogicalResult StoreNdOp::verify() {
485 auto dstTy = getTensorDescType(); // Tile
486 auto valTy = getValueType(); // Vector
487
488 if (!valTy)
489 return emitOpError("Expecting a VectorType result.\n");
490
491 if (!isWriteHintOrNone(getL1HintAttr()))
492 return emitOpError("invalid l1_hint: ") << getL1HintAttr();
493
494 if (!isWriteHintOrNone(getL2HintAttr()))
495 return emitOpError("invalid l2_hint: ") << getL2HintAttr();
496
497 if (!isWriteHintOrNone(getL3HintAttr()))
498 return emitOpError("invalid l3_hint: ") << getL3HintAttr();
499
500 auto array_len = dstTy.getArrayLength();
501 if (array_len > 1)
502 return emitOpError("array length is not supported by store_nd.\n");
503
504 auto tdescElems = dstTy.getNumElements();
505 auto valueElems = valTy.getNumElements();
506
507 // Similar to LoadNdOp, if the value vector is 1D and has less elements than
508 // the tensor descriptor, it is supposed to be a SIMT op. The layout attribute
509 // in tensor_desc is not needed.
510 if (valTy.getRank() == 1 && valueElems < tdescElems) {
511 // SIMT mode doesn't need LayoutAttr.
512 if (dstTy.getLayoutAttr())
513 return emitOpError()
514 << "TensorDesc doesn't need LayoutAttr for SIMT code";
515
516 if (tdescElems % valueElems)
517 return emitOpError()
518 << "Value shape " << makeString(getShapeOf(valTy))
519 << " is not a valid distribution for tensor descriptor " << dstTy;
520
521 return success();
522 }
523
524 // SIMD code should have the same shape as the tensor descriptor.
525 auto tdescShape = getShapeOf(dstTy);
526 auto valueShape = getShapeOf(valTy);
527 if (tdescShape != valueShape)
528 return emitOpError() << "Value shape " << makeString(valueShape)
529 << " is not consistent with tensor descriptor "
530 << dstTy;
531
532 int64_t tDescRank = dstTy.getRank();
533 int64_t offsetSize = getMixedOffsets().size();
534 if (offsetSize != tDescRank)
535 return emitOpError(
536 "Mismatched ranks between offsets and tensor descriptor");
537
538 if (auto layout = getAnchorLayout()) {
539 if (!layout.isDistributable(tdescShape))
540 return emitOpError(
541 "TensorDesc shape is not distributable with the layout");
542 }
543
544 return success();
545}
546
547//===----------------------------------------------------------------------===//
548// XeGPU_PrefetchOp
549//===----------------------------------------------------------------------===//
550LogicalResult PrefetchOp::verify() {
551 if (!isReadHintOrNone(getL1HintAttr()))
552 return emitOpError("invalid l1_hint: ") << getL1HintAttr();
553
554 if (!isReadHintOrNone(getL2HintAttr()))
555 return emitOpError("invalid l2_hint: ") << getL2HintAttr();
556
557 if (!isReadHintOrNone(getL3HintAttr()))
558 return emitOpError("invalid l3_hint: ") << getL3HintAttr();
559
560 auto srcTy = getSourceType();
561 if (srcTy.isInteger() && !getOffsetAlignByteAttr())
562 return emitOpError("offset_align_byte is required with integer source.");
563
564 if (getOffsetAlignByteAttr() && !srcTy.isInteger())
565 return emitOpError("offset_align_byte only allowed with integer source.");
566
567 if (auto layout = getAnchorLayout()) {
568 // get the offset operand and its shape
569 auto offsetsTy = getOffsets().getType();
570 if (llvm::isa<VectorType>(offsetsTy) &&
571 !layout.isDistributable(getShapeOf(offsetsTy)))
572 return emitOpError("offset shape is not distributable with the layout");
573 }
574
575 return success();
576}
577
578//===----------------------------------------------------------------------===//
579// XeGPU_LoadGatherOp
580//===----------------------------------------------------------------------===//
581LogicalResult LoadGatherOp::verify() {
582 auto maskTy = getMaskType();
583 auto valueTy = getValueType();
584
585 if (!isReadHintOrNone(getL1HintAttr()))
586 return emitOpError("invalid l1_hint: ") << getL1HintAttr();
587
588 if (!isReadHintOrNone(getL2HintAttr()))
589 return emitOpError("invalid l2_hint: ") << getL2HintAttr();
590
591 if (!isReadHintOrNone(getL3HintAttr()))
592 return emitOpError("invalid l3_hint: ") << getL3HintAttr();
593
594 auto srcTy = getSourceType();
595 uint64_t chunkSize = static_cast<int64_t>(getChunkSize().value_or(1));
596 auto memTy = dyn_cast<MemRefType>(srcTy);
597
598 if (memTy && (getElementType() != memTy.getElementType()))
599 return emitError() << "Value should have the same element type as MemRef.";
600
601 if (auto layout = getAnchorLayout()) {
602 if (!layout.isDistributable(getShapeOf(valueTy)))
603 return emitOpError("Value shape is not distributable with the layout");
604 }
605
606 auto offsetsTy = getOffsets().getType();
607 if (failed(isValidContiguity(getContiguity(), offsetsTy,
608 [&]() { return emitOpError(); })))
609 return failure();
610 return isValidGatherScatterBufferParams(offsetsTy, maskTy, valueTy, chunkSize,
611 [&]() { return emitOpError(); });
612}
613
614void LoadGatherOp::build(OpBuilder &builder, OperationState &state,
615 Type valueType, Value source,
616 ArrayRef<OpFoldResult> offsets, Value mask,
617 IntegerAttr chunk_size, xegpu::CachePolicyAttr l1_hint,
618 xegpu::CachePolicyAttr l2_hint,
619 xegpu::CachePolicyAttr l3_hint) {
620 auto loc = source.getLoc();
621 int64_t size = static_cast<int64_t>(offsets.size());
622 auto type = VectorType::get(size, builder.getIndexType());
623 auto values = getValueOrCreateConstantIndexOp(builder, loc, offsets);
624 auto offset = vector::FromElementsOp::create(builder, loc, type, values);
625
626 build(builder, state, valueType, source, offset, mask, chunk_size, l1_hint,
627 l2_hint, l3_hint, /*anchor_layout=*/nullptr,
628 /*contiguity=*/nullptr);
629}
630
631void LoadGatherOp::build(OpBuilder &builder, OperationState &state,
632 Type valueType, Value source,
633 ArrayRef<OpFoldResult> offsets, Value mask,
634 IntegerAttr chunk_size, xegpu::CachePolicyAttr l1_hint,
635 xegpu::CachePolicyAttr l2_hint,
636 xegpu::CachePolicyAttr l3_hint,
637 DistributeLayoutAttr layout) {
638 auto loc = source.getLoc();
639 int64_t size = static_cast<int64_t>(offsets.size());
640 auto type = VectorType::get(size, builder.getIndexType());
641 auto values = getValueOrCreateConstantIndexOp(builder, loc, offsets);
642 auto offset = vector::FromElementsOp::create(builder, loc, type, values);
643
644 build(builder, state, valueType, source, offset, mask, chunk_size, l1_hint,
645 l2_hint, l3_hint, layout, /*contiguity=*/nullptr);
646}
647
648//===----------------------------------------------------------------------===//
649// XeGPU_StoreScatterOp
650//===----------------------------------------------------------------------===//
651LogicalResult StoreScatterOp::verify() {
652 auto maskTy = getMaskType();
653 auto valueTy = getValueType();
654
655 if (!isWriteHintOrNone(getL1HintAttr()))
656 return emitOpError("invalid l1_hint: ") << getL1HintAttr();
657
658 if (!isWriteHintOrNone(getL2HintAttr()))
659 return emitOpError("invalid l2_hint: ") << getL2HintAttr();
660
661 if (!isWriteHintOrNone(getL3HintAttr()))
662 return emitOpError("invalid l3_hint: ") << getL3HintAttr();
663
664 auto destTy = getDestType();
665 uint64_t chunkSize = static_cast<int64_t>(getChunkSize().value_or(1));
666 auto memTy = dyn_cast<MemRefType>(destTy);
667
668 if (memTy && (getElementType() != memTy.getElementType()))
669 return emitError() << "Value should have the same element type as MemRef.";
670
671 if (auto layout = getAnchorLayout()) {
672 if (!layout.isDistributable(getShapeOf(valueTy)))
673 return emitOpError("Value shape is not distributable with the layout");
674 }
675
676 auto offsetsTy = getOffsets().getType();
677 if (failed(isValidContiguity(getContiguity(), offsetsTy,
678 [&]() { return emitOpError(); })))
679 return failure();
680 return isValidGatherScatterBufferParams(offsetsTy, maskTy, valueTy, chunkSize,
681 [&]() { return emitOpError(); });
682}
683
684void StoreScatterOp::build(OpBuilder &builder, OperationState &state,
685 Value value, Value dest,
686 ArrayRef<OpFoldResult> offsets, Value mask,
687 IntegerAttr chunk_size,
688 xegpu::CachePolicyAttr l1_hint,
689 xegpu::CachePolicyAttr l2_hint,
690 xegpu::CachePolicyAttr l3_hint) {
691 auto loc = dest.getLoc();
692 int64_t size = static_cast<int64_t>(offsets.size());
693 auto type = VectorType::get(size, builder.getIndexType());
694 auto values = getValueOrCreateConstantIndexOp(builder, loc, offsets);
695 auto offset = vector::FromElementsOp::create(builder, loc, type, values);
696
697 // Call the correct builder overload that does not expect result types.
698 build(builder, state, value, dest, offset, mask, chunk_size, l1_hint, l2_hint,
699 l3_hint, /*anchor_layout=*/nullptr, /*contiguity=*/nullptr);
700}
701
702void StoreScatterOp::build(
703 OpBuilder &builder, OperationState &state, Value value, Value dest,
704 ArrayRef<OpFoldResult> offsets, Value mask, IntegerAttr chunk_size,
705 xegpu::CachePolicyAttr l1_hint, xegpu::CachePolicyAttr l2_hint,
706 xegpu::CachePolicyAttr l3_hint, DistributeLayoutAttr layout) {
707 auto loc = dest.getLoc();
708 int64_t size = static_cast<int64_t>(offsets.size());
709 auto type = VectorType::get(size, builder.getIndexType());
710 auto values = getValueOrCreateConstantIndexOp(builder, loc, offsets);
711 auto offset = vector::FromElementsOp::create(builder, loc, type, values);
712
713 // Call the correct builder overload that does not expect result types.
714 build(builder, state, value, dest, offset, mask, chunk_size, l1_hint, l2_hint,
715 l3_hint, layout, /*contiguity=*/nullptr);
716}
717
718//===----------------------------------------------------------------------===//
719// DPAS Common Verification Helpers
720//===----------------------------------------------------------------------===//
721
722// Helper to verify layout distributability for a value
723static LogicalResult
725 std::optional<DistributeLayoutAttr> layout,
726 ArrayRef<int64_t> shape, StringRef operandName) {
727 if (layout && !layout->isDistributable(
728 SmallVector<int64_t>(shape.begin(), shape.end())))
729 return op->emitOpError(operandName)
730 << " shape is not distributable with the layout";
731 return success();
732}
733
734// Helper to verify M, N, K dimensions match between A, B, and result matrices
735static LogicalResult verifyDpasDimensions(Operation *op,
736 ArrayRef<int64_t> aShape,
737 ArrayRef<int64_t> bShape,
738 ArrayRef<int64_t> resShape) {
739
740 auto aRank = aShape.size();
741 auto bRank = bShape.size();
742 auto resRank = resShape.size();
743 if (aRank == 1 && bRank == 1 && resRank == 1)
744 return success();
745
746 // A must be at least 2D, B must be 2D or 3D (innermost dims), result at
747 // least 2D.
748 if (aRank < 2)
749 return op->emitOpError("A operand must be at least a 2D vector.");
750 if (bRank < 2)
751 return op->emitOpError("B operand must be at least a 2D vector.");
752 if (resRank < 2)
753 return op->emitOpError("Result must be at least a 2D vector.");
754
755 // FIXME: B may have one extra trailing dim for VNNI packing
756 // (B[batch..., K/vnni, N, vnni]). We plan to drop VNNI packing support, so
757 // rather than properly verifying the packed dimensions, we simply accept
758 // the packed form here and skip the detailed verification. This branch
759 // should be removed once VNNI packing support is dropped.
760 if (bRank == aRank + 1)
761 return success();
762
763 // All operands have the same rank. They share the same batch dimensions,
764 // with the last two dims being the core matmul dims: A[batch..., M, K],
765 // B[batch..., K, N], result[batch..., M, N].
766 if (aRank != bRank || aRank != resRank)
767 return op->emitOpError("Rank mismatch among A, B, and result.");
768
769 int64_t batchRank = aRank - 2;
770
771 // Verify batch dimensions match.
772 for (int64_t i = 0; i < batchRank; ++i) {
773 if (aShape[i] != resShape[i])
774 return op->emitOpError("Batch dimension mismatch at dim ")
775 << i << ": A has " << aShape[i] << " but result has "
776 << resShape[i] << ".";
777 if (aShape[i] != bShape[i])
778 return op->emitOpError("Batch dimension mismatch at dim ")
779 << i << ": A has " << aShape[i] << " but B has " << bShape[i]
780 << ".";
781 }
782
783 // Core matmul dimensions (last two dims of each operand).
784 int64_t aM = aShape[batchRank];
785 int64_t aK = aShape[batchRank + 1];
786 int64_t bK = bShape[batchRank];
787 int64_t bN = bShape[batchRank + 1];
788 int64_t resM = resShape[batchRank];
789 int64_t resN = resShape[batchRank + 1];
790
791 // Verify K dimension match between A and B
792 if (bK != aK)
793 return op->emitOpError("K-dimension mismatch: A has K=")
794 << aK << " but B has K=" << bK << ".";
795
796 // Verify M dimension match between A and result
797 if (aM != resM)
798 return op->emitOpError("M-dimension mismatch: A has M=")
799 << aM << " but result has M=" << resM << ".";
800
801 // Verify N dimension match between B and result
802 if (bN != resN)
803 return op->emitOpError("N-dimension mismatch: B has N=")
804 << bN << " but result has N=" << resN << ".";
805
806 return success();
807}
808
809// Helper to verify accumulator matches result type
810static LogicalResult verifyDpasAccumulator(Operation *op, Type accType,
811 Type resultType) {
812 if (accType != resultType)
813 return op->emitOpError("Accumulator type must match result type.");
814 return success();
815}
816
817//===----------------------------------------------------------------------===//
818// XeGPU_DpasOp
819//===----------------------------------------------------------------------===//
820LogicalResult DpasOp::verify() {
821 auto lhsShape = getLhsType().getShape();
822 auto rhsShape = getRhsType().getShape();
823 auto resShape = getResultType().getShape();
824
825 // Verify layout distributability
826 if (failed(
827 verifyLayoutDistributable(*this, getLayoutCd(), resShape, "Result")))
828 return failure();
829 if (failed(verifyLayoutDistributable(*this, getLayoutA(), lhsShape, "A")))
830 return failure();
831 if (failed(verifyLayoutDistributable(*this, getLayoutB(), rhsShape, "B")))
832 return failure();
833
834 // Verify accumulator if present
835 if (getAcc() &&
836 failed(verifyDpasAccumulator(*this, getAcc().getType(), getResultType())))
837 return failure();
838
839 return verifyDpasDimensions(*this, lhsShape, rhsShape, resShape);
840}
841
842//===----------------------------------------------------------------------===//
843// XeGPU_ConvertLayoutOp
844//===----------------------------------------------------------------------===//
845LogicalResult ConvertLayoutOp::verify() {
846 auto srcLayout = getInputLayout();
847 auto resLayout = getTargetLayout();
848 if (!srcLayout)
849 return emitOpError("expected input layout.");
850 if (!resLayout)
851 return emitOpError("expected target layout.");
852
853 // both input and target layouts should be WgLayout or SgLayout at the same
854 // time.
855 if ((!srcLayout.isForWorkgroup() || !resLayout.isForWorkgroup()) &&
856 (!srcLayout.isForSubgroup() || !resLayout.isForSubgroup()))
857 return emitOpError("expected input layout and target layout be WgLayout or "
858 "SgLayout at the same time.");
859
860 Type srcType = getSource().getType();
861 if (llvm::isa<VectorType>(srcType)) {
862 SmallVector<int64_t> shape(llvm::cast<VectorType>(srcType).getShape());
863 if (!srcLayout.isDistributable(shape))
864 return emitOpError(
865 "invalid input layout, data cannot be evenly distributed.");
866
867 if (!resLayout.isDistributable(shape))
868 return emitOpError(
869 "invalid target layout, data cannot be evenly distributed.");
870 }
871 return mlir::success();
872}
873
874//===----------------------------------------------------------------------===//
875// XeGPU_LoadMatrixOp
876//===----------------------------------------------------------------------===//
877void LoadMatrixOp::build(OpBuilder &builder, OperationState &state, Type res,
880 DistributeLayoutAttr layout) {
881 llvm::SmallVector<Value> dynamicOffsets;
882 llvm::SmallVector<int64_t> staticOffsets;
883 dispatchIndexOpFoldResults(offsets, dynamicOffsets, staticOffsets);
884 auto staticOffsetsAttr = builder.getDenseI64ArrayAttr(staticOffsets);
885 // Call the generated builder with all parameters (including optional ones as
886 // nullptr/empty)
887 build(builder, state, res, memDesc, dynamicOffsets, staticOffsetsAttr,
888 /*subgroup_block_io=*/nullptr, layout);
889}
890
891LogicalResult LoadMatrixOp::verify() {
892
893 auto resTy = dyn_cast<VectorType>(getRes().getType());
894 UnitAttr subgroup_block_io = getSubgroupBlockIoAttr();
895 MemDescType mdescTy = getMemDesc().getType();
896
897 return IsValidMatrixOpParams(resTy, mdescTy, subgroup_block_io,
898 getLayoutAttr(), [&]() { return emitError(); });
899}
900
901//===----------------------------------------------------------------------===//
902// XeGPU_StoreMatrixOp
903//===----------------------------------------------------------------------===//
904void StoreMatrixOp::build(OpBuilder &builder, OperationState &state, Value data,
907 DistributeLayoutAttr layout) {
908 llvm::SmallVector<Value> dynamicOffsets;
909 llvm::SmallVector<int64_t> staticOffsets;
910 dispatchIndexOpFoldResults(offsets, dynamicOffsets, staticOffsets);
911 auto staticOffsetsAttr = builder.getDenseI64ArrayAttr(staticOffsets);
912 build(builder, state, data, memDesc, dynamicOffsets, staticOffsetsAttr,
913 /*subgroup_block_io=*/nullptr, layout);
914}
915
916LogicalResult StoreMatrixOp::verify() {
917
918 auto dataTy = dyn_cast<VectorType>(getData().getType());
919 UnitAttr subgroup_block_io = getSubgroupBlockIoAttr();
920 MemDescType mdescTy = getMemDesc().getType();
921 return IsValidMatrixOpParams(dataTy, mdescTy, subgroup_block_io,
922 getLayoutAttr(), [&]() { return emitError(); });
923}
924
925//===----------------------------------------------------------------------===//
926// XeGPU_TruncfOp
927//===----------------------------------------------------------------------===//
928
929LogicalResult TruncfOp::verify() {
930 auto sourceVecType = dyn_cast<VectorType>(getSource().getType());
931 auto resultVecType = dyn_cast<VectorType>(getResult().getType());
932
933 if (sourceVecType.getElementTypeBitWidth() <=
934 resultVecType.getElementTypeBitWidth())
935 return emitOpError("input type must be wider than result type.");
936
937 return success();
938}
939
940//===----------------------------------------------------------------------===//
941// XeGPU_DpasMxOp
942//===----------------------------------------------------------------------===//
943
944LogicalResult DpasMxOp::verify() {
945 auto aShape = getAType().getShape();
946 auto bShape = getBType().getShape();
947 auto resShape = getResultType().getShape();
948
949 // Verify layout distributability for A, B, and result
950 if (failed(
951 verifyLayoutDistributable(*this, getLayoutCd(), resShape, "Result")))
952 return failure();
953 if (failed(verifyLayoutDistributable(*this, getLayoutA(), aShape, "A")))
954 return failure();
955 if (failed(verifyLayoutDistributable(*this, getLayoutB(), bShape, "B")))
956 return failure();
957
958 // Verify accumulator if present
959 if (getAcc() &&
960 failed(verifyDpasAccumulator(*this, getAcc().getType(), getResultType())))
961 return failure();
962
963 // Verify M, N, K dimensions
964 if (failed(verifyDpasDimensions(*this, aShape, bShape, resShape)))
965 return failure();
966
967 // Determine batch rank from A operand.
968 int64_t aBatchRank = aShape.size() - 2;
969
970 // Validate scale_a if present
971 if (getScaleA()) {
972 auto scaleAVecType = dyn_cast<VectorType>(getScaleAType());
973 // Only validate if scale is a vector (scalars are always valid)
974 if (scaleAVecType && scaleAVecType.getRank() > 1) {
975 auto scaleAShape = scaleAVecType.getShape();
976
977 if (scaleAVecType.getRank() < 2)
978 return emitOpError("Scale A must be at least a 2D vector when not a "
979 "scalar.");
980
981 // Verify layout distributability for scale_a
982 if (failed(verifyLayoutDistributable(*this, getLayoutAScale(),
983 scaleAShape, "ScaleA")))
984 return failure();
985
986 // Validate M dimension: scale_a's M must match A's M (last-1 dim)
987 if (scaleAShape[scaleAShape.size() - 2] != aShape[aBatchRank])
988 return emitOpError("Scale A M dimension [")
989 << scaleAShape[scaleAShape.size() - 2]
990 << "] must match A M dimension [" << aShape[aBatchRank] << "].";
991 }
992 }
993
994 // Validate scale_b if present
995 if (getScaleB()) {
996 auto scaleBVecType = dyn_cast<VectorType>(getScaleBType());
997 // Only validate if scale is a vector (scalars are always valid)
998 if (scaleBVecType && scaleBVecType.getRank() > 1) {
999 auto scaleBShape = scaleBVecType.getShape();
1000
1001 if (scaleBVecType.getRank() < 2)
1002 return emitOpError("Scale B must be at least a 2D vector when not a "
1003 "scalar.");
1004
1005 // Verify layout distributability for scale_b
1006 if (failed(verifyLayoutDistributable(*this, getLayoutBScale(),
1007 scaleBShape, "ScaleB")))
1008 return failure();
1009
1010 // Validate N dimension: scale_b's N (last dim) must match B's N (last
1011 // dim)
1012 if (scaleBShape.back() != bShape.back())
1013 return emitOpError("Scale B N dimension [")
1014 << scaleBShape.back() << "] must match B N dimension ["
1015 << bShape.back() << "].";
1016 }
1017 }
1018
1019 // Validate scale K dimension compatibility if both scales are present and
1020 // vectors
1021 if (getScaleA() && getScaleB()) {
1022 auto scaleAVecType = dyn_cast<VectorType>(getScaleAType());
1023 auto scaleBVecType = dyn_cast<VectorType>(getScaleBType());
1024
1025 if (scaleAVecType && scaleBVecType && scaleAVecType.getRank() > 1 &&
1026 scaleBVecType.getRank() > 1) {
1027 auto scaleAShape = scaleAVecType.getShape();
1028 auto scaleBShape = scaleBVecType.getShape();
1029
1030 // Validate scale K dimension compatibility: scale_a's last dim must
1031 // match scale_b's second-to-last dim
1032 if (scaleAShape.back() != scaleBShape[scaleBShape.size() - 2])
1033 return emitOpError("Scale K dimension mismatch: scale_a has K=")
1034 << scaleAShape.back()
1035 << " but scale_b has K=" << scaleBShape[scaleBShape.size() - 2]
1036 << ".";
1037 }
1038 }
1039
1040 return success();
1041}
1042
1043namespace mlir {
1044#include <mlir/Dialect/XeGPU/IR/XeGPUAttrInterface.cpp.inc>
1045} // namespace mlir
1046#include <mlir/Dialect/XeGPU/IR/XeGPUEnums.cpp.inc>
1047#define GET_OP_CLASSES
1048#include <mlir/Dialect/XeGPU/IR/XeGPU.cpp.inc>
return success()
p<< " : "<< getMemRefType()<< ", "<< getType();}static LogicalResult verifyVectorMemoryOp(Operation *op, MemRefType memrefType, VectorType vectorType) { if(memrefType.getElementType() !=vectorType.getElementType()) return op-> emitOpError("requires memref and vector types of the same elemental type")
Given a list of lists of parsed operands, populates uniqueOperands with unique operands.
static Type getElementType(Type type)
Determine the element type of type.
ArrayAttr()
static Type getValueType(Attribute attr)
Definition SPIRVOps.cpp:831
static ArrayRef< int64_t > getShape(Type type)
Returns the shape of the given type.
Definition Traits.cpp:117
static SmallVector< int64_t > getShapeOf(Type type)
Definition XeGPUOps.cpp:39
static LogicalResult verifyDpasAccumulator(Operation *op, Type accType, Type resultType)
Definition XeGPUOps.cpp:810
LogicalResult IsValidMatrixOpParams(VectorType dataTy, MemDescType mdescTy, UnitAttr subgroup_block_io, DistributeLayoutAttr layout, function_ref< InFlightDiagnostic()> emitError)
Definition XeGPUOps.cpp:139
static std::string makeString(T array, bool breakline=false)
Definition XeGPUOps.cpp:25
static bool isWriteHintOrNone(const CachePolicyAttr &attr)
Definition XeGPUOps.cpp:56
static bool isReadHintOrNone(const CachePolicyAttr &attr)
Definition XeGPUOps.cpp:48
static LogicalResult isValidGatherScatterBufferParams(Type offsetsTy, Type maskTy, VectorType valueTy, int64_t chunkSize, function_ref< InFlightDiagnostic()> emitError)
Definition XeGPUOps.cpp:65
static LogicalResult isValidContiguity(std::optional< uint64_t > contiguity, Type offsetsTy, function_ref< InFlightDiagnostic()> emitError)
Definition XeGPUOps.cpp:120
static LogicalResult verifyDpasDimensions(Operation *op, ArrayRef< int64_t > aShape, ArrayRef< int64_t > bShape, ArrayRef< int64_t > resShape)
Definition XeGPUOps.cpp:735
static LogicalResult verifyLayoutDistributable(Operation *op, std::optional< DistributeLayoutAttr > layout, ArrayRef< int64_t > shape, StringRef operandName)
Definition XeGPUOps.cpp:724
Attributes are known-constant values of operations.
Definition Attributes.h:25
DenseI64ArrayAttr getDenseI64ArrayAttr(ArrayRef< int64_t > values)
Definition Builders.cpp:171
IndexType getIndexType()
Definition Builders.cpp:55
This class represents a diagnostic that is inflight and set to be reported.
This class helps build Operations.
Definition Builders.h:209
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
InFlightDiagnostic emitOpError(const Twine &message={})
Emit an error with the op name prefixed, like "'dim' op " which is convenient for verifiers.
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
bool isInteger() const
Return true if this is an integer type (with the specified width).
Definition Types.cpp:58
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Type getType() const
Return the type of this value.
Definition Value.h:105
Location getLoc() const
Return the location of this value.
Definition Value.cpp:24
SmallVector< OpFoldResult > getMixedSizes(OpBuilder &builder, Location loc, Value value)
Return the dimensions of the given memref value.
Definition MemRefOps.cpp:79
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:717
Include the generated interface declarations.
InFlightDiagnostic emitWarning(Location loc)
Utility method to emit a warning message using this location.
detail::DenseArrayAttrImpl< int64_t > DenseI64ArrayAttr
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:307
SmallVector< T > applyPermutation(ArrayRef< T > input, ArrayRef< int64_t > permutation)
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.
std::conditional_t< std::is_same_v< Ty, mlir::Type >, mlir::Value, detail::TypedValue< Ty > > TypedValue
If Ty is mlir::Type this will select Value instead of having a wrapper around it.
Definition Value.h:494
void dispatchIndexOpFoldResults(ArrayRef< OpFoldResult > ofrs, SmallVectorImpl< Value > &dynamicVec, SmallVectorImpl< int64_t > &staticVec)
Helper function to dispatch multiple OpFoldResults according to the behavior of dispatchIndexOpFoldRe...
Value getValueOrCreateConstantIndexOp(OpBuilder &b, Location loc, OpFoldResult ofr)
Converts an OpFoldResult to a Value.
Definition Utils.cpp:114
llvm::function_ref< Fn > function_ref
Definition LLVM.h:147
This represents an operation in an abstracted form, suitable for use with the builder APIs.