MLIR 24.0.0git
MemRefToEmitC.cpp
Go to the documentation of this file.
1//===- MemRefToEmitC.cpp - MemRef to EmitC conversion ---------------------===//
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//
9// This file implements patterns to convert memref ops into emitc ops.
10//
11//===----------------------------------------------------------------------===//
12
14
18#include "mlir/IR/Builders.h"
20#include "mlir/IR/Diagnostics.h"
22#include "mlir/IR/TypeRange.h"
23#include "mlir/IR/Value.h"
25#include "llvm/ADT/STLExtras.h"
26#include <cstdint>
27#include <numeric>
28
29using namespace mlir;
30
31static bool isMemRefTypeLegalForEmitC(MemRefType memRefType) {
32 return memRefType.hasStaticShape() && memRefType.getLayout().isIdentity() &&
33 !llvm::is_contained(memRefType.getShape(), 0);
34}
35
36namespace {
37/// Implement the interface to convert MemRef to EmitC.
38struct MemRefToEmitCDialectInterface : public ConvertToEmitCPatternInterface {
39 MemRefToEmitCDialectInterface(Dialect *dialect)
40 : ConvertToEmitCPatternInterface(dialect) {}
41
42 /// Hook for derived dialect interface to provide conversion patterns
43 /// and mark dialect legal for the conversion target.
44 void populateConvertToEmitCConversionPatterns(
45 ConversionTarget &target, TypeConverter &typeConverter,
46 RewritePatternSet &patterns, std::optional<bool> lowerToCpp) const final {
47 populateMemRefToEmitCConversionPatterns(patterns, typeConverter);
48 }
49};
50} // namespace
51
53 registry.addExtension(+[](MLIRContext *ctx, memref::MemRefDialect *dialect) {
54 dialect->addInterfaces<MemRefToEmitCDialectInterface>();
55 });
56}
57
58//===----------------------------------------------------------------------===//
59// Conversion Patterns
60//===----------------------------------------------------------------------===//
61
62namespace {
63struct ConvertAlloca final : public OpConversionPattern<memref::AllocaOp> {
64 using OpConversionPattern::OpConversionPattern;
65
66 LogicalResult
67 matchAndRewrite(memref::AllocaOp op, OpAdaptor operands,
68 ConversionPatternRewriter &rewriter) const override {
69
70 if (!op.getType().hasStaticShape()) {
71 return rewriter.notifyMatchFailure(
72 op.getLoc(), "cannot transform alloca with dynamic shape");
73 }
74
75 if (op.getAlignment().value_or(1) > 1) {
76 // TODO: Allow alignment if it is not more than the natural alignment
77 // of the C array.
78 return rewriter.notifyMatchFailure(
79 op.getLoc(), "cannot transform alloca with alignment requirement");
80 }
81
82 Type resultTy = getTypeConverter()->convertType(op.getType());
83 if (!resultTy)
84 return rewriter.notifyMatchFailure(op.getLoc(), "cannot convert type");
85
86 auto noInit = emitc::OpaqueAttr::get(getContext(), "");
87 // Rank-0 path
88 if (op.getType().getRank() == 0) {
89 auto pointerTy = dyn_cast<emitc::PointerType>(resultTy);
90 assert(pointerTy && "expected rank-0 MemRef to convert to pointer");
91 Type elemTy = pointerTy.getPointee();
92 auto var = emitc::VariableOp::create(
93 rewriter, op.getLoc(), emitc::LValueType::get(elemTy), noInit);
94
95 auto ptr = emitc::AddressOfOp::create(rewriter, op.getLoc(), resultTy,
96 var.getResult());
97
98 rewriter.replaceOp(op, ptr.getResult());
99 return success();
100 }
101 // Rank > 0 path
102 rewriter.replaceOpWithNewOp<emitc::VariableOp>(op, resultTy, noInit);
103 return success();
104 }
105};
106
107static Value calculateMemrefTotalSizeBytes(Location loc, MemRefType memrefType,
108 OpBuilder &builder,
109 Type convertedElementType) {
110 assert(isMemRefTypeLegalForEmitC(memrefType) &&
111 "incompatible memref type for EmitC conversion");
112
113 emitc::CallOpaqueOp elementSize = emitc::CallOpaqueOp::create(
114 builder, loc, emitc::SizeTType::get(builder.getContext()),
115 builder.getStringAttr("sizeof"), ValueRange{},
116 ArrayAttr::get(builder.getContext(),
117 {TypeAttr::get(convertedElementType)}));
118
119 IndexType indexType = builder.getIndexType();
120 int64_t numElements = llvm::product_of(memrefType.getShape());
121 emitc::ConstantOp numElementsValue = emitc::ConstantOp::create(
122 builder, loc, indexType, builder.getIndexAttr(numElements));
123
124 Type sizeTType = emitc::SizeTType::get(builder.getContext());
125 emitc::MulOp totalSizeBytes = emitc::MulOp::create(
126 builder, loc, sizeTType, elementSize.getResult(0), numElementsValue);
127
128 return totalSizeBytes.getResult();
129}
130
131static emitc::AddressOfOp
132createPointerFromEmitcArray(Location loc, OpBuilder &builder,
133 TypedValue<emitc::ArrayType> arrayValue) {
134
135 emitc::ConstantOp zeroIndex = emitc::ConstantOp::create(
136 builder, loc, builder.getIndexType(), builder.getIndexAttr(0));
137
138 emitc::ArrayType arrayType = arrayValue.getType();
139 llvm::SmallVector<mlir::Value> indices(arrayType.getRank(), zeroIndex);
140 emitc::SubscriptOp subPtr =
141 emitc::SubscriptOp::create(builder, loc, arrayValue, ValueRange(indices));
142 emitc::AddressOfOp ptr = emitc::AddressOfOp::create(
143 builder, loc, emitc::PointerType::get(arrayType.getElementType()),
144 subPtr);
145
146 return ptr;
147}
148
149static Value getMemRefPointer(Value v) {
150 if (isa<emitc::PointerType>(v.getType()))
151 return v;
152
153 // If `v` is defined through an unrealized cast and the source of that cast
154 // is `emitc.ptr`, return the pointer.
155 if (auto cast = v.getDefiningOp<UnrealizedConversionCastOp>())
156 if (cast.getNumOperands() == 1 &&
157 isa<emitc::PointerType>(cast.getOperand(0).getType()))
158 return cast.getOperand(0);
159 return Value();
160}
161
162static Value computeRowMajorLinearIndex(ImplicitLocOpBuilder &builder,
163 MemRefType memrefType,
165 ArrayRef<int64_t> shape = memrefType.getShape();
166
167 Type idxType =
168 indices.empty() ? builder.getIndexType() : indices[0].getType();
169
170 Value linearIndex =
171 indices.empty()
172 ? emitc::ConstantOp::create(builder, idxType, builder.getIndexAttr(0))
173 : indices[0];
174
175 if (indices.empty())
176 return linearIndex;
177
178 for (auto [dim, idx] : llvm::zip(shape.drop_front(), indices.drop_front())) {
179 Value dimSize =
180 emitc::ConstantOp::create(builder, idxType, builder.getIndexAttr(dim));
181 linearIndex = emitc::MulOp::create(builder, idxType, linearIndex, dimSize);
182 linearIndex = emitc::AddOp::create(builder, idxType, linearIndex, idx);
183 }
184 return linearIndex;
185}
186
187struct ConvertAlloc final : public OpConversionPattern<memref::AllocOp> {
188 using OpConversionPattern::OpConversionPattern;
189 LogicalResult
190 matchAndRewrite(memref::AllocOp allocOp, OpAdaptor operands,
191 ConversionPatternRewriter &rewriter) const override {
192 Location loc = allocOp.getLoc();
193 MemRefType memrefType = allocOp.getType();
194 if (!isMemRefTypeLegalForEmitC(memrefType)) {
195 return rewriter.notifyMatchFailure(
196 loc, "incompatible memref type for EmitC conversion");
197 }
198
199 Type sizeTType = emitc::SizeTType::get(rewriter.getContext());
200 Type elementType =
201 getTypeConverter()->convertType(memrefType.getElementType());
202 if (!elementType) {
203 return rewriter.notifyMatchFailure(
204 loc, "failed to convert memref element type");
205 }
206 IndexType indexType = rewriter.getIndexType();
207 Value totalSizeBytes =
208 calculateMemrefTotalSizeBytes(loc, memrefType, rewriter, elementType);
209
210 emitc::CallOpaqueOp allocCall;
211 StringAttr allocFunctionName;
212 Value alignmentValue;
213 SmallVector<Value, 2> argsVec;
214 if (allocOp.getAlignment()) {
215 allocFunctionName = rewriter.getStringAttr(alignedAllocFunctionName);
216 alignmentValue = emitc::ConstantOp::create(
217 rewriter, loc, sizeTType,
218 rewriter.getIntegerAttr(indexType,
219 allocOp.getAlignment().value_or(0)));
220 argsVec.push_back(alignmentValue);
221 } else {
222 allocFunctionName = rewriter.getStringAttr(mallocFunctionName);
223 }
224
225 argsVec.push_back(totalSizeBytes);
226 ValueRange args(argsVec);
227
228 allocCall = emitc::CallOpaqueOp::create(
229 rewriter, loc,
230 emitc::PointerType::get(
231 emitc::OpaqueType::get(rewriter.getContext(), "void")),
232 allocFunctionName, args);
233
234 emitc::PointerType targetPointerType = emitc::PointerType::get(elementType);
235 emitc::CastOp castOp = emitc::CastOp::create(
236 rewriter, loc, targetPointerType, allocCall.getResult(0));
237
238 rewriter.replaceOp(allocOp, castOp);
239 return success();
240 }
241};
242
243struct ConvertDealloc final : public OpConversionPattern<memref::DeallocOp> {
244 using OpConversionPattern::OpConversionPattern;
245
246 LogicalResult
247 matchAndRewrite(memref::DeallocOp deallocOp, OpAdaptor operands,
248 ConversionPatternRewriter &rewriter) const override {
249 Location loc = deallocOp.getLoc();
250 // `free` can only be emitted when the dealloc operand is recoverable as an
251 // `emitc.ptr<T>`.
252 Value ptr = getMemRefPointer(operands.getMemref());
253 if (!ptr) {
254 return rewriter.notifyMatchFailure(
255 loc, "expected pointer-backed memref for EmitC deallocation");
256 }
257
258 // The allocation APIs used by MemRefToEmitC return `void *`, and `free`
259 // expects that same pointer type. Deallocation therefore only needs the
260 // recovered base pointer cast back to `void *` before calling `free`.
261 Type opaqueVoidPtrType = emitc::PointerType::get(
262 emitc::OpaqueType::get(rewriter.getContext(), "void"));
263 Value freeArg =
264 emitc::CastOp::create(rewriter, loc, opaqueVoidPtrType, ptr);
265 emitc::CallOpaqueOp freeCall = emitc::CallOpaqueOp::create(
266 rewriter, loc, TypeRange{}, rewriter.getStringAttr(freeFunctionName),
267 ValueRange{freeArg});
268 rewriter.replaceOp(deallocOp, freeCall.getResults());
269 return success();
270 }
271};
272
273struct ConvertCopy final : public OpConversionPattern<memref::CopyOp> {
274 using OpConversionPattern::OpConversionPattern;
275
276 LogicalResult
277 matchAndRewrite(memref::CopyOp copyOp, OpAdaptor operands,
278 ConversionPatternRewriter &rewriter) const override {
279 Location loc = copyOp.getLoc();
280 MemRefType srcMemrefType = cast<MemRefType>(copyOp.getSource().getType());
281 MemRefType targetMemrefType =
282 cast<MemRefType>(copyOp.getTarget().getType());
283
284 if (!isMemRefTypeLegalForEmitC(srcMemrefType))
285 return rewriter.notifyMatchFailure(
286 loc, "incompatible source memref type for EmitC conversion");
287
288 if (!isMemRefTypeLegalForEmitC(targetMemrefType))
289 return rewriter.notifyMatchFailure(
290 loc, "incompatible target memref type for EmitC conversion");
291
292 if (srcMemrefType.getRank() == 0) {
293 assert(targetMemrefType.getRank() == 0 &&
294 "target must have same rank as source");
295 Type elementType =
296 getTypeConverter()->convertType(srcMemrefType.getElementType());
297 if (!elementType)
298 return rewriter.notifyMatchFailure(loc, "cannot convert element type");
299
300 Value srcPtr = getMemRefPointer(operands.getSource());
301 Value targetPtr = getMemRefPointer(operands.getTarget());
302 if (!srcPtr || !targetPtr)
303 return rewriter.notifyMatchFailure(loc, "expected pointer operands");
304
305 Value zeroIndex = emitc::ConstantOp::create(
306 rewriter, loc, rewriter.getIndexType(), rewriter.getIndexAttr(0));
307 Value srcLValue = emitc::SubscriptOp::create(
308 rewriter, loc, cast<TypedValue<emitc::PointerType>>(srcPtr),
309 zeroIndex);
310 Value value =
311 emitc::LoadOp::create(rewriter, loc, elementType, srcLValue);
312
313 Value targetLValue = emitc::SubscriptOp::create(
314 rewriter, loc, cast<TypedValue<emitc::PointerType>>(targetPtr),
315 zeroIndex);
316 rewriter.replaceOpWithNewOp<emitc::AssignOp>(copyOp, targetLValue, value);
317 return success();
318 }
319
320 auto srcArrayValue =
321 cast<TypedValue<emitc::ArrayType>>(operands.getSource());
322 emitc::AddressOfOp srcPtr =
323 createPointerFromEmitcArray(loc, rewriter, srcArrayValue);
324
325 auto targetArrayValue =
326 cast<TypedValue<emitc::ArrayType>>(operands.getTarget());
327 emitc::AddressOfOp targetPtr =
328 createPointerFromEmitcArray(loc, rewriter, targetArrayValue);
329
330 Type convertedElementType =
331 getTypeConverter()->convertType(srcMemrefType.getElementType());
332 if (!convertedElementType) {
333 return rewriter.notifyMatchFailure(
334 loc, "failed to convert memref element type");
335 }
336 Value totalSizeInBytes = calculateMemrefTotalSizeBytes(
337 loc, srcMemrefType, rewriter, convertedElementType);
338 emitc::CallOpaqueOp memCpyCall =
339 emitc::CallOpaqueOp::create(rewriter, loc, TypeRange{}, "memcpy",
341 targetPtr.getResult(),
342 srcPtr.getResult(),
343 totalSizeInBytes,
344 });
345
346 rewriter.replaceOp(copyOp, memCpyCall.getResults());
347
348 return success();
349 }
350};
351
352struct ConvertGlobal final : public OpConversionPattern<memref::GlobalOp> {
353 using OpConversionPattern::OpConversionPattern;
354
355 LogicalResult
356 matchAndRewrite(memref::GlobalOp op, OpAdaptor operands,
357 ConversionPatternRewriter &rewriter) const override {
358 MemRefType opTy = op.getType();
359 if (!op.getType().hasStaticShape()) {
360 return rewriter.notifyMatchFailure(
361 op.getLoc(), "cannot transform global with dynamic shape");
362 }
363
364 if (op.getAlignment().value_or(1) > 1) {
365 // TODO: Extend GlobalOp to specify alignment via the `alignas` specifier.
366 return rewriter.notifyMatchFailure(
367 op.getLoc(), "global variable with alignment requirement is "
368 "currently not supported");
369 }
370
371 Type resultTy = getTypeConverter()->convertType(opTy);
372
373 if (!resultTy) {
374 return rewriter.notifyMatchFailure(op.getLoc(),
375 "cannot convert result type");
376 }
377
379 if (visibility != SymbolTable::Visibility::Public &&
380 visibility != SymbolTable::Visibility::Private) {
381 return rewriter.notifyMatchFailure(
382 op.getLoc(),
383 "only public and private visibility is currently supported");
384 }
385 // We are explicit in specifying the linkage because the default linkage
386 // for constants is different in C and C++.
387 bool staticSpecifier = visibility == SymbolTable::Visibility::Private;
388 bool externSpecifier = !staticSpecifier;
389
390 Attribute initialValue = operands.getInitialValueAttr();
391 if (opTy.getRank() == 0) {
392 auto pointerTy = dyn_cast<emitc::PointerType>(resultTy);
393 assert(pointerTy && "expected rank-0 MemRef to convert to pointer");
394 resultTy = pointerTy.getPointee();
395 // special case for `variable : memref<i32> = dense<-1>`
396 if (std::optional<Attribute> initValueAttr = op.getInitialValue()) {
397 if (auto elementsAttr = llvm::dyn_cast<ElementsAttr>(*initValueAttr)) {
398 initialValue = elementsAttr.getSplatValue<Attribute>();
399 }
400 }
401 }
402 if (isa_and_present<UnitAttr>(initialValue))
403 initialValue = {};
404
405 rewriter.replaceOpWithNewOp<emitc::GlobalOp>(
406 op, operands.getSymName(), resultTy, initialValue, externSpecifier,
407 staticSpecifier, operands.getConstant());
408 return success();
409 }
410};
411
412struct ConvertGetGlobal final
413 : public OpConversionPattern<memref::GetGlobalOp> {
414 using OpConversionPattern::OpConversionPattern;
415
416 LogicalResult
417 matchAndRewrite(memref::GetGlobalOp op, OpAdaptor operands,
418 ConversionPatternRewriter &rewriter) const override {
419
420 MemRefType opTy = op.getType();
421 Type resultTy = getTypeConverter()->convertType(opTy);
422
423 if (!resultTy) {
424 return rewriter.notifyMatchFailure(op.getLoc(),
425 "cannot convert result type");
426 }
427
428 if (opTy.getRank() == 0) {
429 auto pointerTy = dyn_cast<emitc::PointerType>(resultTy);
430 assert(pointerTy && "expected rank-0 MemRef to convert to pointer");
431 Type elemTy = pointerTy.getPointee();
432 emitc::LValueType lvalueType = emitc::LValueType::get(elemTy);
433 emitc::GetGlobalOp globalLValue = emitc::GetGlobalOp::create(
434 rewriter, op.getLoc(), lvalueType, operands.getNameAttr());
435 rewriter.replaceOpWithNewOp<emitc::AddressOfOp>(op, resultTy,
436 globalLValue);
437 return success();
438 }
439 rewriter.replaceOpWithNewOp<emitc::GetGlobalOp>(op, resultTy,
440 operands.getNameAttr());
441 return success();
442 }
443};
444
445struct ConvertLoad final : public OpConversionPattern<memref::LoadOp> {
446 using OpConversionPattern::OpConversionPattern;
447
448 LogicalResult
449 matchAndRewrite(memref::LoadOp op, OpAdaptor operands,
450 ConversionPatternRewriter &rewriter) const override {
451 Location loc = op.getLoc();
452 auto resultTy = getTypeConverter()->convertType(op.getType());
453 if (!resultTy) {
454 return rewriter.notifyMatchFailure(loc, "cannot convert type");
455 }
456
457 auto arrayValue =
458 dyn_cast<TypedValue<emitc::ArrayType>>(operands.getMemref());
459 Value ptr = getMemRefPointer(operands.getMemref());
460 if (!ptr && arrayValue) {
461 auto subscript = emitc::SubscriptOp::create(rewriter, loc, arrayValue,
462 operands.getIndices());
463
464 rewriter.replaceOpWithNewOp<emitc::LoadOp>(op, resultTy, subscript);
465 return success();
466 }
467
468 if (!ptr)
469 return rewriter.notifyMatchFailure(loc, "expected array or pointer type");
470 MemRefType opMemrefType = cast<MemRefType>(op.getMemref().getType());
471 ValueRange indices = operands.getIndices();
472
473 ImplicitLocOpBuilder b(loc, rewriter);
474 Value linearIndex = computeRowMajorLinearIndex(b, opMemrefType, indices);
475 auto typedPtr = cast<TypedValue<emitc::PointerType>>(ptr);
476 auto subscript =
477 emitc::SubscriptOp::create(rewriter, loc, typedPtr, linearIndex);
478
479 rewriter.replaceOpWithNewOp<emitc::LoadOp>(op, resultTy, subscript);
480 return success();
481 }
482};
483
484struct ConvertStore final : public OpConversionPattern<memref::StoreOp> {
485 using OpConversionPattern::OpConversionPattern;
486
487 LogicalResult
488 matchAndRewrite(memref::StoreOp op, OpAdaptor operands,
489 ConversionPatternRewriter &rewriter) const override {
490 Location loc = op.getLoc();
491 auto arrayValue =
492 dyn_cast<TypedValue<emitc::ArrayType>>(operands.getMemref());
493 Value ptr = getMemRefPointer(operands.getMemref());
494 if (!ptr && arrayValue) {
495 auto subscript = emitc::SubscriptOp::create(rewriter, loc, arrayValue,
496 operands.getIndices());
497 rewriter.replaceOpWithNewOp<emitc::AssignOp>(op, subscript,
498 operands.getValue());
499 return success();
500 }
501
502 if (!ptr)
503 return rewriter.notifyMatchFailure(loc, "expected array or pointer type");
504 MemRefType opMemrefType = cast<MemRefType>(op.getMemref().getType());
505 ValueRange indices = operands.getIndices();
506
507 ImplicitLocOpBuilder b(loc, rewriter);
508 Value linearIndex = computeRowMajorLinearIndex(b, opMemrefType, indices);
509 auto typedPtr = cast<TypedValue<emitc::PointerType>>(ptr);
510 auto subscript =
511 emitc::SubscriptOp::create(rewriter, loc, typedPtr, linearIndex);
512
513 rewriter.replaceOpWithNewOp<emitc::AssignOp>(op, subscript,
514 operands.getValue());
515 return success();
516 }
517};
518
519} // namespace
520
522 RewritePatternSet &patterns, const TypeConverter &converter) {
523 patterns.add<ConvertAlloca, ConvertAlloc, ConvertCopy, ConvertDealloc,
524 ConvertGlobal, ConvertGetGlobal, ConvertLoad, ConvertStore>(
525 converter, patterns.getContext());
526}
return success()
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
b getContext())
static bool isMemRefTypeLegalForEmitC(MemRefType memRefType)
constexpr const char * mallocFunctionName
constexpr const char * freeFunctionName
constexpr const char * alignedAllocFunctionName
IntegerAttr getIndexAttr(int64_t value)
Definition Builders.cpp:116
StringAttr getStringAttr(const Twine &bytes)
Definition Builders.cpp:271
MLIRContext * getContext() const
Definition Builders.h:56
IndexType getIndexType()
Definition Builders.cpp:59
The DialectRegistry maps a dialect namespace to a constructor for the matching dialect.
bool addExtension(TypeID extensionID, std::unique_ptr< DialectExtensionBase > extension)
Add the given extension to the registry.
ImplicitLocOpBuilder maintains a 'current location', allowing use of the create<> method without spec...
Definition Builders.h:633
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
This class helps build Operations.
Definition Builders.h:210
MLIRContext * getContext() const
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
static Visibility getSymbolVisibility(Operation *symbol)
Returns the visibility of the given symbol operation.
Visibility
An enumeration detailing the different visibility types that a symbol may have.
Definition SymbolTable.h:90
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
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
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
Include the generated interface declarations.
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 populateMemRefToEmitCConversionPatterns(RewritePatternSet &patterns, const TypeConverter &converter)
void registerConvertMemRefToEmitCInterface(DialectRegistry &registry)
LogicalResult matchAndRewrite(spirv::LoadOp loadOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(spirv::StoreOp storeOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override