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