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