MLIR 24.0.0git
RaiseWasmMLIR.cpp
Go to the documentation of this file.
1//===- RaiseWasmMLIR.cpp - Convert Wasm to less abstract dialects ---*- C++
2//-*-===//
3//
4// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5// See https://llvm.org/LICENSE.txt for license information.
6// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7//
8//===----------------------------------------------------------------------===//
9//
10// This file implements lowering of wasm operations to standard dialects ops.
11//
12//===----------------------------------------------------------------------===//
13
15
26#include "mlir/IR/ValueRange.h"
29#include "llvm/Support/LogicalResult.h"
30#include <optional>
31
32#define DEBUG_TYPE "wasm-convert"
33
34namespace mlir {
35#define GEN_PASS_DEF_RAISEWASMMLIR
36#include "mlir/Conversion/Passes.h.inc"
37} // namespace mlir
38
39using namespace mlir;
40using namespace mlir::wasmssa;
41namespace {
42
43template <typename OpTy>
44static typename OpTy::Properties getDefaultProperties(MLIRContext *context) {
45 typename OpTy::Properties properties{};
46 if constexpr (!std::is_same_v<typename OpTy::Properties, EmptyProperties>)
47 OpTy::populateDefaultProperties(
48 OperationName(OpTy::getOperationName(), context), properties);
49 return properties;
50}
51
52template <typename SourceOp, typename TargetIntOp, typename TargetFPOp>
53struct IntFPDispatchMappingConversion : OpConversionPattern<SourceOp> {
54 using OpConversionPattern<SourceOp>::OpConversionPattern;
55
56 LogicalResult
57 matchAndRewrite(SourceOp srcOp, typename SourceOp::Adaptor adaptor,
58 ConversionPatternRewriter &rewriter) const override {
59 Type type = srcOp.getRhs().getType();
60 if (type.isInteger()) {
61 rewriter.replaceOpWithNewOp<TargetIntOp>(
62 srcOp, srcOp->getResultTypes(), adaptor.getOperands(),
63 getDefaultProperties<TargetIntOp>(rewriter.getContext()),
64 ArrayRef<NamedAttribute>{});
65 return success();
66 }
67 if (!type.isFloat())
68 return failure();
69 rewriter.replaceOpWithNewOp<TargetFPOp>(
70 srcOp, srcOp->getResultTypes(), adaptor.getOperands(),
71 getDefaultProperties<TargetFPOp>(rewriter.getContext()),
72 ArrayRef<NamedAttribute>{});
73 return success();
74 }
75};
76
77using WasmAddOpConversion =
78 IntFPDispatchMappingConversion<AddOp, arith::AddIOp, arith::AddFOp>;
79using WasmMulOpConversion =
80 IntFPDispatchMappingConversion<MulOp, arith::MulIOp, arith::MulFOp>;
81using WasmSubOpConversion =
82 IntFPDispatchMappingConversion<SubOp, arith::SubIOp, arith::SubFOp>;
83
84/// Convert a k-ary source operation \p SourceOp into an operation \p TargetOp.
85/// Both \p SourceOp and \p TargetOp must have the same number of operands.
86template <typename SourceOp, typename TargetOp>
87struct OpMappingConversion : OpConversionPattern<SourceOp> {
88 using OpConversionPattern<SourceOp>::OpConversionPattern;
89
90 LogicalResult
91 matchAndRewrite(SourceOp srcOp, typename SourceOp::Adaptor adaptor,
92 ConversionPatternRewriter &rewriter) const override {
93 rewriter.replaceOpWithNewOp<TargetOp>(
94 srcOp, srcOp->getResultTypes(), adaptor.getOperands(),
95 getDefaultProperties<TargetOp>(rewriter.getContext()),
96 ArrayRef<NamedAttribute>{});
97 return success();
98 }
99};
100
101using WasmAndOpConversion = OpMappingConversion<AndOp, arith::AndIOp>;
102using WasmCeilOpConversion = OpMappingConversion<CeilOp, math::CeilOp>;
103/// TODO: SIToFP and UIToFP don't allow specification of the floating point
104/// rounding mode
105using WasmConvertSOpConversion =
106 OpMappingConversion<ConvertSOp, arith::SIToFPOp>;
107using WasmConvertUOpConversion =
108 OpMappingConversion<ConvertUOp, arith::UIToFPOp>;
109using WasmDemoteOpConversion = OpMappingConversion<DemoteOp, arith::TruncFOp>;
110using WasmDivFPOpConversion = OpMappingConversion<DivOp, arith::DivFOp>;
111using WasmDivSIOpConversion = OpMappingConversion<DivSIOp, arith::DivSIOp>;
112using WasmDivUIOpConversion = OpMappingConversion<DivUIOp, arith::DivUIOp>;
113using WasmExtendSOpConversion =
114 OpMappingConversion<ExtendSI32Op, arith::ExtSIOp>;
115using WasmExtendUOpConversion =
116 OpMappingConversion<ExtendUI32Op, arith::ExtUIOp>;
117using WasmFloorOpConversion = OpMappingConversion<FloorOp, math::FloorOp>;
118using WasmMaxOpConversion = OpMappingConversion<MaxOp, arith::MaximumFOp>;
119using WasmMinOpConversion = OpMappingConversion<MinOp, arith::MinimumFOp>;
120using WasmOrOpConversion = OpMappingConversion<OrOp, arith::OrIOp>;
121using WasmPromoteOpConversion = OpMappingConversion<PromoteOp, arith::ExtFOp>;
122using WasmRemSIOpConversion = OpMappingConversion<RemSIOp, arith::RemSIOp>;
123using WasmRemUIOpConversion = OpMappingConversion<RemUIOp, arith::RemUIOp>;
124using WasmReinterpretOpConversion =
125 OpMappingConversion<ReinterpretOp, arith::BitcastOp>;
126using WasmShLOpConversion = OpMappingConversion<ShLOp, arith::ShLIOp>;
127using WasmShRSOpConversion = OpMappingConversion<ShRSOp, arith::ShRSIOp>;
128using WasmShRUOpConversion = OpMappingConversion<ShRUOp, arith::ShRUIOp>;
129using WasmXOrOpConversion = OpMappingConversion<XOrOp, arith::XOrIOp>;
130using WasmNegOpConversion = OpMappingConversion<NegOp, arith::NegFOp>;
131using WasmCopySignOpConversion =
132 OpMappingConversion<CopySignOp, math::CopySignOp>;
133using WasmClzOpConversion =
134 OpMappingConversion<ClzOp, math::CountLeadingZerosOp>;
135using WasmCtzOpConversion =
136 OpMappingConversion<CtzOp, math::CountTrailingZerosOp>;
137using WasmPopCntOpConversion = OpMappingConversion<PopCntOp, math::CtPopOp>;
138using WasmAbsOpConversion = OpMappingConversion<AbsOp, math::AbsFOp>;
139using WasmTruncOpConversion = OpMappingConversion<TruncOp, math::TruncOp>;
140using WasmSqrtOpConversion = OpMappingConversion<SqrtOp, math::SqrtOp>;
141using WasmWrapOpConversion = OpMappingConversion<WrapOp, arith::TruncIOp>;
142
143/// Lower a rotate to a series of bitwise operations. Intended for us
144/// in dialects that do not natively support rotate operations.
145///
146/// Result stays in the wasm dialect. It will then subsequently be lowered to
147/// the target dialect.
148///
149/// The rotate will be lowered to a pattern like so:
150///
151/// (val LHSShiftOp (bits & (width-1))) | (val RHSShiftOp (-bits & (width-1)))
152///
153/// Where LHSShiftOp and RHSShiftOp are shift operations. Concretely,
154///
155/// rotr = (val >> (bits & (width - 1))) | (val << (-bits & (width - 1)))
156/// rotl = (val << (bits & (width - 1))) | (val >> (-bits & (width - 1)))
157///
158/// Using this variant ensures that our rotate is defined in the target dialect.
159///
160/// \p SourceOp - Rotate operation to replace.
161/// \p LHSShiftOp - Shift operation to use on the left-hand side of the OR.
162/// \p RHSShiftOp - Shift operation to use on the right-hand side of the OR.
163template <typename SourceOp, typename LHSShiftOp, typename RHSShiftOp>
164struct RotateOpConversion : OpConversionPattern<SourceOp> {
165 using OpConversionPattern<SourceOp>::OpConversionPattern;
166
167 LogicalResult
168 matchAndRewrite(SourceOp srcOp, typename SourceOp::Adaptor adaptor,
169 ConversionPatternRewriter &rewriter) const override {
170 const Type ty = srcOp->getResultTypes()[0];
171 const Location loc = srcOp->getLoc();
172 const Value val = adaptor.getVal();
173 const Value bits = adaptor.getBits();
174 const unsigned width = ty.getIntOrFloatBitWidth();
175
176 // Materialize (width - 1) for use in both sides of the expression.
177 auto cstWidthMinusOne =
178 ConstOp::create(rewriter, loc, IntegerAttr::get(ty, width - 1));
179
180 // Form the left-hand side of the OR:
181 // (val (lhs shift op) (bits & (width - 1)))
182 auto orLHS = LHSShiftOp::create(
183 rewriter, loc, val,
184 AndOp::create(rewriter, loc, bits, cstWidthMinusOne));
185
186 // Form the right-hand side of the OR:
187 // (val (rhs shift op) (-bits & (width - 1)))
188 auto orRHS = RHSShiftOp::create(
189 rewriter, loc, val,
190 // (-bits & (width - 1))
191 AndOp::create(rewriter, loc,
192 // 0 - bits == -bits
193 SubOp::create(rewriter, loc,
194 ConstOp::create(rewriter, loc,
195 IntegerAttr::get(ty, 0)),
196 bits),
197 cstWidthMinusOne));
198
199 // OR together the two shifts and replace the rotate with the new
200 // expression.
201 rewriter.replaceOpWithNewOp<OrOp>(srcOp, orLHS, orRHS);
202 return success();
203 }
204};
205
206using WasmRotrOpConversion = RotateOpConversion<RotrOp, ShRUOp, ShLOp>;
207using WasmRotlOpConversion = RotateOpConversion<RotlOp, ShLOp, ShRUOp>;
208
209template <typename SourceOp, typename TargetOp, typename AttrType,
210 typename ValType, ValType flag>
211struct ComparisonOpConversion : OpConversionPattern<SourceOp> {
212 using OpConversionPattern<SourceOp>::OpConversionPattern;
213
214 LogicalResult
215 matchAndRewrite(SourceOp srcOp, typename SourceOp::Adaptor adaptor,
216 ConversionPatternRewriter &rewriter) const override {
217 auto cmpRes =
218 TargetOp::create(rewriter, srcOp.getLoc(), rewriter.getI1Type(),
219 AttrType::get(rewriter.getContext(), flag),
220 adaptor.getLhs(), adaptor.getRhs())
221 .getResult();
222 rewriter.replaceOpWithNewOp<arith::ExtUIOp>(srcOp, rewriter.getI32Type(),
223 cmpRes);
224
225 return success();
226 }
227};
228
229template <typename SourceOp, arith::CmpFPredicate compFlag>
230using FPComparisonConversion =
231 ComparisonOpConversion<SourceOp, arith::CmpFOp, arith::CmpFPredicateAttr,
232 arith::CmpFPredicate, compFlag>;
233
234template <typename SourceOp, arith::CmpIPredicate compFlag>
235using IntComparisonConversion =
236 ComparisonOpConversion<SourceOp, arith::CmpIOp, arith::CmpIPredicateAttr,
237 arith::CmpIPredicate, compFlag>;
238
239using WasmLtSIOpConversion =
240 IntComparisonConversion<LtSIOp, arith::CmpIPredicate::slt>;
241using WasmLeSIOpConversion =
242 IntComparisonConversion<LeSIOp, arith::CmpIPredicate::sle>;
243using WasmGtSIOpConversion =
244 IntComparisonConversion<GtSIOp, arith::CmpIPredicate::sgt>;
245using WasmGeSIOpConversion =
246 IntComparisonConversion<GeSIOp, arith::CmpIPredicate::sge>;
247using WasmLtUIOpConversion =
248 IntComparisonConversion<LtUIOp, arith::CmpIPredicate::ult>;
249using WasmLeUIOpConversion =
250 IntComparisonConversion<LeUIOp, arith::CmpIPredicate::ule>;
251using WasmGtUIOpConversion =
252 IntComparisonConversion<GtUIOp, arith::CmpIPredicate::ugt>;
253using WasmGeUIOpConversion =
254 IntComparisonConversion<GeUIOp, arith::CmpIPredicate::uge>;
255using WasmLtOpConversion =
256 FPComparisonConversion<LtOp, arith::CmpFPredicate::OLT>;
257using WasmLeOpConversion =
258 FPComparisonConversion<LeOp, arith::CmpFPredicate::OLE>;
259using WasmGtOpConversion =
260 FPComparisonConversion<GtOp, arith::CmpFPredicate::OGT>;
261using WasmGeOpConversion =
262 FPComparisonConversion<GeOp, arith::CmpFPredicate::OGE>;
263
264template <typename SourceOp, arith::CmpIPredicate IntFlag,
265 arith::CmpFPredicate FloatFlag>
266struct IntFpComparisonOpConversion : OpConversionPattern<SourceOp> {
267 using OpConversionPattern<SourceOp>::OpConversionPattern;
268
269 LogicalResult
270 matchAndRewrite(SourceOp srcOp, typename SourceOp::Adaptor adaptor,
271 ConversionPatternRewriter &rewriter) const override {
272 Value comparisonResult;
273 if (srcOp.getLhs().getType().isInteger())
274 comparisonResult =
275 arith::CmpIOp::create(
276 rewriter, srcOp.getLoc(), rewriter.getI1Type(),
277 arith::CmpIPredicateAttr::get(rewriter.getContext(), IntFlag),
278 adaptor.getLhs(), adaptor.getRhs())
279 .getResult();
280 else if (srcOp.getLhs().getType().isFloat())
281 comparisonResult =
282 arith::CmpFOp::create(
283 rewriter, srcOp.getLoc(), rewriter.getI1Type(),
284 arith::CmpFPredicateAttr::get(rewriter.getContext(), FloatFlag),
285 adaptor.getLhs(), adaptor.getRhs())
286 .getResult();
287 else
288 return rewriter.notifyMatchFailure(
289 srcOp.getLoc(), "Unsupported datatype for comparison OP.");
290
291 rewriter.replaceOpWithNewOp<arith::ExtUIOp>(srcOp, rewriter.getI32Type(),
292 comparisonResult);
293 return success();
294 }
295};
296
297using WasmEqOpConversion =
298 IntFpComparisonOpConversion<EqOp, arith::CmpIPredicate::eq,
299 arith::CmpFPredicate::OEQ>;
300using WasmNeOpConversion =
301 IntFpComparisonOpConversion<NeOp, arith::CmpIPredicate::ne,
302 arith::CmpFPredicate::ONE>;
303
304struct WasmCallOpConversion : OpConversionPattern<FuncCallOp> {
305 using OpConversionPattern::OpConversionPattern;
306
307 LogicalResult
308 matchAndRewrite(FuncCallOp funcCallOp, FuncCallOp::Adaptor adaptor,
309 ConversionPatternRewriter &rewriter) const override {
310 rewriter.replaceOpWithNewOp<func::CallOp>(
311 funcCallOp, funcCallOp.getCallee(), funcCallOp.getResults().getTypes(),
312 funcCallOp.getOperands());
313 return success();
314 }
315};
316
317struct WasmConstOpConversion : OpConversionPattern<ConstOp> {
318 using OpConversionPattern::OpConversionPattern;
319
320 LogicalResult
321 matchAndRewrite(ConstOp constOp, ConstOp::Adaptor adaptor,
322 ConversionPatternRewriter &rewriter) const override {
323 rewriter.replaceOpWithNewOp<arith::ConstantOp>(constOp, constOp.getValue());
324 return success();
325 }
326};
327
328struct WasmEqzOpConversion : OpConversionPattern<EqzOp> {
329 using OpConversionPattern::OpConversionPattern;
330
331 LogicalResult
332 matchAndRewrite(EqzOp eqzOp, EqzOp::Adaptor adaptor,
333 ConversionPatternRewriter &rewriter) const override {
334 auto loc = eqzOp->getLoc();
335 auto zero = arith::ConstantOp::create(
336 rewriter, loc,
337 rewriter.getIntegerAttr(adaptor.getInput().getType(), 0))
338 .getResult();
339 auto cmpRes = arith::CmpIOp::create(
340 rewriter, loc, rewriter.getI1Type(),
341 arith::CmpIPredicateAttr::get(rewriter.getContext(),
342 arith::CmpIPredicate::eq),
343 adaptor.getInput(), zero)
344 .getResult();
345 rewriter.replaceOpWithNewOp<arith::ExtUIOp>(eqzOp, rewriter.getI32Type(),
346 cmpRes);
347
348 return success();
349 }
350};
351
352struct WasmExtendLowBitsOpConversion : OpConversionPattern<ExtendLowBitsSOp> {
353 using OpConversionPattern::OpConversionPattern;
354
355 LogicalResult
356 matchAndRewrite(ExtendLowBitsSOp extendLowBytesSOp,
357 ExtendLowBitsSOp::Adaptor adaptor,
358 ConversionPatternRewriter &rewriter) const override {
359 auto truncWidth = extendLowBytesSOp.getBitsToTake().getInt();
360 auto truncation = arith::TruncIOp::create(
361 rewriter, extendLowBytesSOp->getLoc(),
362 rewriter.getIntegerType(truncWidth), adaptor.getInput());
363 rewriter.replaceOpWithNewOp<arith::ExtSIOp>(
364 extendLowBytesSOp, extendLowBytesSOp.getResult().getType(),
365 truncation.getResult());
366 return success();
367 }
368};
369
370struct WasmFuncImportOpConversion : OpConversionPattern<FuncImportOp> {
371 using OpConversionPattern::OpConversionPattern;
372
373 LogicalResult
374 matchAndRewrite(FuncImportOp funcImportOp, FuncImportOp::Adaptor,
375 ConversionPatternRewriter &rewriter) const override {
376 auto nFunc = rewriter.replaceOpWithNewOp<func::FuncOp>(
377 funcImportOp, funcImportOp.getSymName(), funcImportOp.getType());
378 nFunc.setVisibility(SymbolTable::Visibility::Private);
379 return success();
380 }
381};
382
383struct WasmFuncOpConversion : OpConversionPattern<FuncOp> {
384 using OpConversionPattern::OpConversionPattern;
385 ///
386 /// Control flow conversion needs shared state for tracking which block
387 /// corresponds to which operation at which level.
388 ///
389 /// This class handles such tracking and performs the conversion of control
390 /// flow related ops contained in a function.
391 class CFRewriterVisitor {
392 private:
393 using branch_to_dest_t = llvm::DenseMap<LabelBranchingOpInterface, Block *>;
394 Value getCompResultAsI1(Value compResult,
395 ConversionPatternRewriter &rewriter) {
396 auto testValue = arith::ConstantOp::create(rewriter, compResult.getLoc(),
397 rewriter.getI32IntegerAttr(0));
398 auto flag = arith::CmpIOp::create(
399 rewriter, compResult.getLoc(), rewriter.getIntegerType(1),
400 arith::CmpIPredicate::ne, compResult, testValue)
401 .getResult();
402 return flag;
403 }
404
405 void replaceNestLevelWithBranch(BlockOp blockOp,
406 llvm::ArrayRef<Block *> regionsToEntry,
407 ConversionPatternRewriter &rewriter) {
408 rewriter.replaceOpWithNewOp<cf::BranchOp>(blockOp, regionsToEntry[0],
409 blockOp->getOperands());
410 }
411
412 void replaceNestLevelWithBranch(LoopOp loopOp,
413 llvm::ArrayRef<Block *> regionsToEntry,
414 ConversionPatternRewriter &rewriter) {
415 rewriter.replaceOpWithNewOp<cf::BranchOp>(loopOp, regionsToEntry[0],
416 loopOp->getOperands());
417 }
418
419 void replaceNestLevelWithBranch(IfOp ifOp,
420 llvm::ArrayRef<Block *> regionsToEntry,
421 ConversionPatternRewriter &rewriter) {
422 Block *falseDest =
423 regionsToEntry.size() == 2 ? regionsToEntry[1] : ifOp.getTarget();
424 auto flag = getCompResultAsI1(ifOp.getCondition(), rewriter);
425 rewriter.replaceOpWithNewOp<cf::CondBranchOp>(
426 ifOp, flag, regionsToEntry[0], ifOp.getInputs(), falseDest,
427 ifOp.getInputs());
428 }
429
430 template <typename LevelType>
431 LogicalResult
432 replaceNestLevelWithBranchWrapper(LabelLevelOpInterface nestingOp,
433 llvm::ArrayRef<Block *> regionsToEntry,
434 ConversionPatternRewriter &rewriter) {
435 auto cast = dyn_cast<LevelType>(nestingOp.getOperation());
436 if (!cast)
437 return failure();
438 replaceNestLevelWithBranch(cast, regionsToEntry, rewriter);
439 return success();
440 }
441
442 template <typename... LevelTypes>
443 LogicalResult inlineNestDispatcher(LabelLevelOpInterface nestingOp,
444 ConversionPatternRewriter &rewriter) {
445 auto sip = rewriter.saveInsertionPoint();
446 Block *blockSuccessor = nestingOp->getSuccessor(0);
447 llvm::SmallVector<Block *, 2> regionEntries;
448 LLVM_DEBUG(llvm::dbgs()
449 << "Starting inlining blocks for " << nestingOp << "\n";);
450 for (auto &region : nestingOp->getRegions()) {
451 if (region.empty())
452 continue;
453 regionEntries.push_back(&region.front());
454 /// Inline blocks of nested ops
455 llvm::SmallVector<LabelLevelOpInterface> nestedOps{
456 region.getOps<LabelLevelOpInterface>()};
457 for (auto nestedOp : nestedOps) {
458 LLVM_DEBUG(llvm::dbgs() << " Found nested op: " << nestedOp);
459 if (failed(inlineBlocks(nestedOp, rewriter)))
460 return failure();
461 }
462 rewriter.inlineRegionBefore(region, blockSuccessor);
463 }
464 LLVM_DEBUG(llvm::dbgs() << "End of region inlining\n");
465 LLVM_DEBUG(llvm::dbgs() << "Replacing initial op with branching\n");
466 rewriter.setInsertionPoint(nestingOp);
467 auto res = success(
468 (... || succeeded(replaceNestLevelWithBranchWrapper<LevelTypes>(
469 nestingOp, regionEntries, rewriter))));
470 rewriter.restoreInsertionPoint(sip);
471 if (failed(res))
472 return emitError(nestingOp->getLoc(),
473 "Unable to inline the operation regions.");
474 return success();
475 }
476
477 /// Take a nesting level defining op and inline it in the parent region.
478 LogicalResult inlineBlocks(LabelLevelOpInterface nestingOp,
479 ConversionPatternRewriter &rewriter) {
480 return inlineNestDispatcher<BlockOp, IfOp, LoopOp>(nestingOp, rewriter);
481 }
482
483 llvm::FailureOr<Block *> getBlockFor(LabelBranchingOpInterface branchOp) {
484 auto destIter = branchToDest.find(branchOp);
485 if (destIter == branchToDest.end())
486 return branchOp->emitError("No indexed label op for this operation: ")
487 << branchOp;
488 return destIter->second;
489 }
490
491 inline void convertBranch(BranchIfOp brOp, Block *dest,
492 ConversionPatternRewriter &rewriter) {
493 auto flag = getCompResultAsI1(brOp.getCondition(), rewriter);
494 rewriter.replaceOpWithNewOp<cf::CondBranchOp>(
495 brOp, flag, dest, brOp.getInputs(), brOp.getElseSuccessor(),
496 ValueRange{});
497 }
498
499 inline void convertBranch(BlockReturnOp brOp, Block *dest,
500 ConversionPatternRewriter &rewriter) {
501 rewriter.replaceOpWithNewOp<cf::BranchOp>(brOp, dest, brOp.getInputs());
502 }
503
504 template <typename LevelInterfaceT>
505 inline LogicalResult
506 convertBranchWrapper(LabelBranchingOpInterface branchOp, Block *dest,
507 ConversionPatternRewriter &rewriter) {
508 auto cast = dyn_cast<LevelInterfaceT>(branchOp.getOperation());
509 if (!cast)
510 return failure();
511 auto sip = rewriter.saveInsertionPoint();
512 rewriter.setInsertionPoint(branchOp);
513 convertBranch(cast, dest, rewriter);
514 rewriter.restoreInsertionPoint(sip);
515 return success();
516 }
517
518 template <typename... BranchInterfaceT>
519 LogicalResult convertBranchDispatch(LabelBranchingOpInterface branchOp,
520 ConversionPatternRewriter &rewriter) {
521 auto dest = getBlockFor(branchOp);
522 if (failed(dest))
523 return failure();
524 auto res =
525 success((... || succeeded(convertBranchWrapper<BranchInterfaceT>(
526 branchOp, *dest, rewriter))));
527 if (failed(res))
528 return emitError(branchOp->getLoc(), "No known converter for op ")
529 << branchOp;
530 return res;
531 }
532
533 LogicalResult convertBranch(LabelBranchingOpInterface branchOp,
534 ConversionPatternRewriter &rewriter) {
535 return convertBranchDispatch<BlockReturnOp, BranchIfOp>(branchOp,
536 rewriter);
537 }
538
539 func::FuncOp func;
540 branch_to_dest_t branchToDest;
541
542 public:
543 CFRewriterVisitor(func::FuncOp func) : func{func} {
544 func.walk([this](LabelBranchingOpInterface branchOp) {
545 branchToDest.insert({branchOp, branchOp.getTarget()});
546 });
547 }
548 LogicalResult rewrite(ConversionPatternRewriter &rewriter) {
549 llvm::SmallVector<LabelLevelOpInterface> nestingOps{
550 func.getOps<LabelLevelOpInterface>()};
551 for (auto nestingOp : nestingOps)
552 if (failed(inlineBlocks(nestingOp, rewriter)))
553 return failure();
554
555 auto res =
556 func->walk([this, &rewriter](LabelBranchingOpInterface branchOp) {
557 if (failed(convertBranch(branchOp, rewriter)))
558 return WalkResult::interrupt();
559 return WalkResult::advance();
560 });
561 return failure(res.wasInterrupted());
562 }
563 };
564
565 LogicalResult
566 matchAndRewrite(FuncOp funcOp, FuncOp::Adaptor adaptor,
567 ConversionPatternRewriter &rewriter) const override {
568 auto newFunc =
569 func::FuncOp::create(rewriter, funcOp->getLoc(), funcOp.getSymName(),
570 funcOp.getFunctionType());
571 rewriter.cloneRegionBefore(funcOp.getBody(), newFunc.getBody(),
572 newFunc.getBody().end());
573 Block *oldEntryBlock = &newFunc.getBody().front();
574 auto blockArgTypes = oldEntryBlock->getArgumentTypes();
575 TypeConverter::SignatureConversion sC{oldEntryBlock->getNumArguments()};
576 auto numArgs = blockArgTypes.size();
577 for (size_t i = 0; i < numArgs; ++i) {
578 auto argType = dyn_cast<LocalRefType>(blockArgTypes[i]);
579 if (!argType)
580 return failure();
581 sC.addInputs(i, argType.getElementType());
582 }
583
584 rewriter.applySignatureConversion(oldEntryBlock, sC, getTypeConverter());
585 rewriter.replaceOp(funcOp, newFunc);
586 CFRewriterVisitor cfRewriter{newFunc};
587 return cfRewriter.rewrite(rewriter);
588 }
589};
590
591struct WasmGlobalImportOpConverter : OpConversionPattern<GlobalImportOp> {
592 using OpConversionPattern::OpConversionPattern;
593 LogicalResult
594 matchAndRewrite(GlobalImportOp gIOp, GlobalImportOp::Adaptor adaptor,
595 ConversionPatternRewriter &rewriter) const override {
596 auto memrefGOp = rewriter.replaceOpWithNewOp<memref::GlobalOp>(
597 gIOp, gIOp.getSymNameAttr(), rewriter.getStringAttr("nested"),
598 TypeAttr::get(MemRefType::get({1}, gIOp.getType())), Attribute{},
599 /*constant*/ UnitAttr{},
600 /*alignment*/ IntegerAttr{});
601 memrefGOp.setConstant(!gIOp.getIsMutable());
602 return success();
603 }
604};
605
606template <typename CRTP, typename OriginOpType>
607struct GlobalOpConverter : OpConversionPattern<GlobalOp> {
608 using OpConversionPattern::OpConversionPattern;
609 LogicalResult
610 matchAndRewrite(GlobalOp globalOp, GlobalOp::Adaptor adaptor,
611 ConversionPatternRewriter &rewriter) const override {
612 ReturnOp rop = globalOp.getInitTerminator();
613
614 if (rop->getNumOperands() != 1)
615 return rewriter.notifyMatchFailure(
616 globalOp, "globalOp initializer should return one value exactly");
617
618 auto initializerOp =
619 dyn_cast<OriginOpType>(rop->getOperand(0).getDefiningOp());
620
621 if (!initializerOp)
622 return rewriter.notifyMatchFailure(
623 globalOp, "invalid initializer op type for this pattern");
624
625 return static_cast<CRTP const *>(this)->handleInitializer(
626 globalOp, rewriter, initializerOp);
627 }
628};
629
630struct WasmGlobalWithConstInitConversion
631 : GlobalOpConverter<WasmGlobalWithConstInitConversion, ConstOp> {
632 using GlobalOpConverter::GlobalOpConverter;
633 LogicalResult handleInitializer(GlobalOp globalOp,
634 ConversionPatternRewriter &rewriter,
635 ConstOp constInit) const {
636 auto initializer =
637 DenseElementsAttr::get(RankedTensorType::get({1}, globalOp.getType()),
638 ArrayRef<Attribute>{constInit.getValueAttr()});
639 auto globalReplacement = rewriter.replaceOpWithNewOp<memref::GlobalOp>(
640 globalOp, globalOp.getSymNameAttr(), rewriter.getStringAttr("private"),
641 TypeAttr::get(MemRefType::get({1}, globalOp.getType())), initializer,
642 /*constant*/ UnitAttr{},
643 /*alignment*/ IntegerAttr{});
644 globalReplacement.setConstant(!globalOp.getIsMutable());
645 return success();
646 }
647};
648
649struct WasmGlobalWithGetGlobalInitConversion
650 : GlobalOpConverter<WasmGlobalWithGetGlobalInitConversion, GlobalGetOp> {
651 using GlobalOpConverter::GlobalOpConverter;
652 LogicalResult handleInitializer(GlobalOp globalOp,
653 ConversionPatternRewriter &rewriter,
654 GlobalGetOp constInit) const {
655 auto globalReplacement = rewriter.replaceOpWithNewOp<memref::GlobalOp>(
656 globalOp, globalOp.getSymNameAttr(), rewriter.getStringAttr("private"),
657 TypeAttr::get(MemRefType::get({1}, globalOp.getType())),
658 rewriter.getUnitAttr(),
659 /*constant*/ UnitAttr{},
660 /*alignment*/ IntegerAttr{});
661 globalReplacement.setConstant(!globalOp.getIsMutable());
662 auto loc = globalOp.getLoc();
663 auto initializerName = (globalOp.getSymName() + "::initializer").str();
664 auto globalInitializer =
665 func::FuncOp::create(rewriter, loc, initializerName,
666 FunctionType::get(getContext(), {}, {}));
667 globalInitializer->setAttr(rewriter.getStringAttr("initializer"),
668 rewriter.getUnitAttr());
669 auto *initializerBody = globalInitializer.addEntryBlock();
670 auto sip = rewriter.saveInsertionPoint();
671 rewriter.setInsertionPointToStart(initializerBody);
672 auto srcGlobalPtr = memref::GetGlobalOp::create(
673 rewriter, loc, MemRefType::get({1}, constInit.getType()),
674 constInit.getGlobal());
675 auto destGlobalPtr =
676 memref::GetGlobalOp::create(rewriter, loc, globalReplacement.getType(),
677 globalReplacement.getSymName());
678 auto idx = arith::ConstantIndexOp::create(rewriter, loc, 0).getResult();
679 auto loadSrc =
680 memref::LoadOp::create(rewriter, loc, srcGlobalPtr, ValueRange{idx});
681 memref::StoreOp::create(rewriter, loc, loadSrc.getResult(),
682 destGlobalPtr.getResult(), ValueRange{idx});
683 func::ReturnOp::create(rewriter, loc);
684 rewriter.restoreInsertionPoint(sip);
685 return success();
686 }
687};
688
689struct WasmGlobalSetOpConversion : OpConversionPattern<GlobalSetOp> {
690 using OpConversionPattern::OpConversionPattern;
691 LogicalResult
692 matchAndRewrite(GlobalSetOp globalSetOp, GlobalSetOp::Adaptor adaptor,
693 ConversionPatternRewriter &rewriter) const override {
694 auto loc = globalSetOp.getLoc();
695 auto globalPtr = memref::GetGlobalOp::create(
696 rewriter, loc, MemRefType::get({1}, adaptor.getValue().getType()),
697 globalSetOp.getGlobal());
698 auto idx = arith::ConstantIndexOp::create(rewriter, loc, 0);
699 rewriter.replaceOpWithNewOp<memref::StoreOp>(
700 globalSetOp, adaptor.getValue(), globalPtr.getResult(),
701 ValueRange{idx.getResult()});
702 return success();
703 }
704};
705
706struct WasmMemoryOpConversion : OpConversionPattern<MemOp> {
707 using OpConversionPattern::OpConversionPattern;
708
709 LogicalResult
710 matchAndRewrite(MemOp memOp, MemOp::Adaptor adaptor,
711 ConversionPatternRewriter &rewriter) const override {
712 auto loc = memOp.getLoc();
713 auto bufferType =
714 MemRefType::get({ShapedType::kDynamic}, rewriter.getI8Type());
715 auto bufferPtrType = MemRefType::get({1}, bufferType);
716 auto memVisibility = memOp.getVisibility();
717 // Convert to StringAttr since memref::GlobalOp expects visibility as a
718 // string attribute.
719 mlir::StringAttr visAttr;
720 if (memVisibility == mlir::SymbolTable::Visibility::Public)
721 visAttr = mlir::StringAttr::get(memOp->getContext(), "public");
722 else if (memVisibility == mlir::SymbolTable::Visibility::Private)
723 visAttr = mlir::StringAttr::get(memOp->getContext(), "private");
724 else
725 visAttr = mlir::StringAttr::get(memOp->getContext(), "nested");
726
727 auto memPtr = rewriter.replaceOpWithNewOp<memref::GlobalOp>(
728 memOp, memOp.getSymNameAttr(), visAttr, TypeAttr::get(bufferPtrType),
729 /*initialValue*/ rewriter.getUnitAttr(),
730 /*constant*/ UnitAttr{}, /*alignment*/ IntegerAttr{});
731 auto initializerName = (memPtr.getSymName() + "::initializer").str();
732 auto memInitializer =
733 func::FuncOp::create(rewriter, loc, initializerName,
734 FunctionType::get(getContext(), {}, {}));
735 memInitializer->setAttr(rewriter.getStringAttr("initializer"),
736 rewriter.getUnitAttr());
737 auto *initializerBody = memInitializer.addEntryBlock();
738 auto sip = rewriter.saveInsertionPoint();
739 rewriter.setInsertionPointToStart(initializerBody);
740 auto memRefPtr = memref::GetGlobalOp::create(
741 rewriter, loc, MemRefType::get({1}, bufferType), memPtr.getSymName());
742 auto alloc = memref::AllocOp::create(
743 rewriter, loc,
744 MemRefType::get({memOp.getLimits().getMin()}, rewriter.getI8Type()));
745 auto castOp =
746 memref::CastOp::create(rewriter, loc, bufferType, alloc.getResult());
747 auto idx = arith::ConstantIndexOp::create(rewriter, loc, 0);
748 memref::StoreOp::create(rewriter, loc, castOp.getResult(),
749 memRefPtr.getResult(), ValueRange{idx.getResult()});
750 func::ReturnOp::create(rewriter, loc);
751 rewriter.restoreInsertionPoint(sip);
752 func::CallOp::create(rewriter, loc, memInitializer);
753 return success();
754 }
755};
756
757inline TypedAttr getInitializerAttr(Type t) {
758 assert(t.isIntOrFloat() &&
759 "This helper is intended to use with int and float types");
760 if (t.isInteger())
761 return IntegerAttr::get(t, 0);
762 if (t.isFloat())
763 return FloatAttr::get(t, 0.);
764 return TypedAttr{};
765}
766
767struct WasmLocalConversion : OpConversionPattern<LocalOp> {
768 using OpConversionPattern::OpConversionPattern;
769 LogicalResult
770 matchAndRewrite(LocalOp localOp, LocalOp::Adaptor adaptor,
771 ConversionPatternRewriter &rewriter) const override {
772 auto alloca = rewriter.replaceOpWithNewOp<memref::AllocaOp>(
773 localOp,
774 MemRefType::get({}, localOp.getResult().getType().getElementType()));
775 auto initializer = arith::ConstantOp::create(
776 rewriter, localOp->getLoc(),
777 getInitializerAttr(localOp.getResult().getType().getElementType()));
778 memref::StoreOp::create(rewriter, localOp->getLoc(),
779 initializer.getResult(), alloca.getResult());
780 return success();
781 }
782};
783
784struct WasmLocalGetConversion : OpConversionPattern<LocalGetOp> {
785 using OpConversionPattern::OpConversionPattern;
786 LogicalResult
787 matchAndRewrite(LocalGetOp localGetOp, LocalGetOp::Adaptor adaptor,
788 ConversionPatternRewriter &rewriter) const override {
789 rewriter.replaceOpWithNewOp<memref::LoadOp>(
790 localGetOp, localGetOp.getResult().getType(), adaptor.getLocalVar(),
791 ValueRange{});
792 return success();
793 }
794};
795
796struct WasmLocalSetConversion : OpConversionPattern<LocalSetOp> {
797 using OpConversionPattern::OpConversionPattern;
798 LogicalResult
799 matchAndRewrite(LocalSetOp localSetOp, LocalSetOp::Adaptor adaptor,
800 ConversionPatternRewriter &rewriter) const override {
801 rewriter.replaceOpWithNewOp<memref::StoreOp>(
802 localSetOp, adaptor.getValue(), adaptor.getLocalVar(), ValueRange{});
803 return success();
804 }
805};
806
807struct WasmLocalTeeConversion : OpConversionPattern<LocalTeeOp> {
808 using OpConversionPattern::OpConversionPattern;
809 LogicalResult
810 matchAndRewrite(LocalTeeOp localTeeOp, LocalTeeOp::Adaptor adaptor,
811 ConversionPatternRewriter &rewriter) const override {
812 memref::StoreOp::create(rewriter, localTeeOp->getLoc(), adaptor.getValue(),
813 adaptor.getLocalVar());
814 rewriter.replaceOp(localTeeOp, adaptor.getValue());
815 return success();
816 }
817};
818
819struct WasmReturnOpConversion : OpConversionPattern<ReturnOp> {
820 using OpConversionPattern::OpConversionPattern;
821
822 LogicalResult
823 matchAndRewrite(ReturnOp returnOp, ReturnOp::Adaptor adaptor,
824 ConversionPatternRewriter &rewriter) const override {
825 rewriter.replaceOpWithNewOp<func::ReturnOp>(returnOp,
826 adaptor.getOperands());
827 return success();
828 }
829};
830
831struct WasmSelectOpConversion : OpConversionPattern<SelectOp> {
832 using OpConversionPattern::OpConversionPattern;
833
834 LogicalResult
835 matchAndRewrite(SelectOp selectOp, SelectOp::Adaptor adaptor,
836 ConversionPatternRewriter &rewriter) const override {
837 auto loc = selectOp.getLoc();
838 auto zero =
839 arith::ConstantOp::create(rewriter, loc, rewriter.getI32IntegerAttr(0));
840 auto flag = arith::CmpIOp::create(rewriter, loc, arith::CmpIPredicate::ne,
841 adaptor.getCondition(), zero.getResult());
842 rewriter.replaceOpWithNewOp<arith::SelectOp>(selectOp, flag.getResult(),
843 adaptor.getTrueValue(),
844 adaptor.getFalseValue());
845 return success();
846 }
847};
848
849struct RaiseWasmMLIRPass : public impl::RaiseWasmMLIRBase<RaiseWasmMLIRPass> {
850 void runOnOperation() override {
851 ConversionTarget target{getContext()};
852 target.addIllegalDialect<WasmSSADialect>();
853 target.addLegalDialect<arith::ArithDialect, BuiltinDialect,
854 cf::ControlFlowDialect, func::FuncDialect,
855 memref::MemRefDialect, math::MathDialect>();
856 RewritePatternSet patterns(&getContext());
857 TypeConverter tc{};
858 tc.addConversion([](Type type) -> std::optional<Type> { return type; });
859 tc.addConversion([](LocalRefType type) -> std::optional<Type> {
860 return MemRefType::get({}, type.getElementType());
861 });
862 tc.addTargetMaterialization([](OpBuilder &builder, MemRefType destType,
863 ValueRange values, Location loc) -> Value {
864 if (values.size() != 1 ||
865 values.front().getType() != destType.getElementType())
866 return {};
867 auto localVar = memref::AllocaOp::create(builder, loc, destType);
868 memref::StoreOp::create(builder, loc, values.front(),
869 localVar.getResult());
870 return localVar.getResult();
871 });
873
874 llvm::DenseMap<StringAttr, StringAttr> idxSymToImportSym{};
875 auto *topOp = getOperation();
876 topOp->walk([&idxSymToImportSym, this](ImportOpInterface importOp) {
877 auto const qualifiedImportName = importOp.getQualifiedImportName();
878 auto qualNameAttr = StringAttr::get(&getContext(), qualifiedImportName);
879 idxSymToImportSym.insert(
880 std::make_pair(importOp.getSymbolName(), qualNameAttr));
881 });
882
883 if (failed(applyFullConversion(topOp, target, std::move(patterns))))
884 return signalPassFailure();
885
886 auto symTable = SymbolTable{topOp};
887 for (auto &[oldName, newName] : idxSymToImportSym) {
888 if (failed(symTable.rename(oldName, newName)))
889 return signalPassFailure();
890 }
891 }
892};
893} // namespace
894
896 TypeConverter &tc, RewritePatternSet &patternSet) {
897 auto *ctx = patternSet.getContext();
898 // Disable clang-format in patternSet for readability + small diffs.
899 // clang-format off
900 patternSet
901 .add<
902 WasmAbsOpConversion,
903 WasmAddOpConversion,
904 WasmAndOpConversion,
905 WasmCallOpConversion,
906 WasmCeilOpConversion,
907 WasmClzOpConversion,
908 WasmConstOpConversion,
909 WasmConvertSOpConversion,
910 WasmConvertUOpConversion,
911 WasmCopySignOpConversion,
912 WasmCtzOpConversion,
913 WasmDemoteOpConversion,
914 WasmDivFPOpConversion,
915 WasmDivSIOpConversion,
916 WasmDivUIOpConversion,
917 WasmEqOpConversion,
918 WasmEqzOpConversion,
919 WasmExtendLowBitsOpConversion,
920 WasmExtendSOpConversion,
921 WasmExtendUOpConversion,
922 WasmFloorOpConversion,
923 WasmFuncImportOpConversion,
924 WasmFuncOpConversion,
925 WasmGeOpConversion,
926 WasmGeSIOpConversion,
927 WasmGeUIOpConversion,
928 WasmGlobalImportOpConverter,
929 WasmGlobalSetOpConversion,
930 WasmGlobalWithConstInitConversion,
931 WasmGlobalWithGetGlobalInitConversion,
932 WasmGtOpConversion,
933 WasmGtSIOpConversion,
934 WasmGtUIOpConversion,
935 WasmLeOpConversion,
936 WasmLeSIOpConversion,
937 WasmLeUIOpConversion,
938 WasmLocalConversion,
939 WasmLocalGetConversion,
940 WasmLocalSetConversion,
941 WasmLocalTeeConversion,
942 WasmLtOpConversion,
943 WasmLtSIOpConversion,
944 WasmLtUIOpConversion,
945 WasmMaxOpConversion,
946 WasmMemoryOpConversion,
947 WasmMinOpConversion,
948 WasmMulOpConversion,
949 WasmNeOpConversion,
950 WasmNegOpConversion,
951 WasmOrOpConversion,
952 WasmPopCntOpConversion,
953 WasmPromoteOpConversion,
954 WasmReinterpretOpConversion,
955 WasmRemSIOpConversion,
956 WasmRemUIOpConversion,
957 WasmReturnOpConversion,
958 WasmRotlOpConversion,
959 WasmRotrOpConversion,
960 WasmSelectOpConversion,
961 WasmShLOpConversion,
962 WasmShRSOpConversion,
963 WasmShRUOpConversion,
964 WasmSqrtOpConversion,
965 WasmSubOpConversion,
966 WasmTruncOpConversion,
967 WasmWrapOpConversion,
968 WasmXOrOpConversion
969 >(tc, ctx);
970 // clang-format on
971}
972
973std::unique_ptr<Pass> createRaiseWasmMLIRPass() {
974 return std::make_unique<RaiseWasmMLIRPass>();
975}
return success()
b getContext())
std::unique_ptr< Pass > createRaiseWasmMLIRPass()
static void rewrite(DataFlowSolver &solver, MLIRContext *context, MutableArrayRef< Region > initialRegions)
Rewrite the given regions using the computing analysis.
Definition SCCP.cpp:67
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
ValueTypeRange< BlockArgListType > getArgumentTypes()
Return a range containing the types of the arguments for this block.
Definition Block.cpp:154
unsigned getNumArguments()
Definition Block.h:152
static DenseElementsAttr get(ShapedType type, ArrayRef< Attribute > values)
Constructs a dense elements attribute from an array of element values.
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
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.
@ Public
The symbol is public and may be referenced anywhere internal or external to the visible references in...
Definition SymbolTable.h:87
@ Private
The symbol is private and may only be referenced by SymbolRefAttrs local to the operations within the...
Definition SymbolTable.h:91
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
bool isFloat() const
Return true if this is an float type (with the specified width).
Definition Types.cpp:47
bool isInteger() const
Return true if this is an integer type (with the specified width).
Definition Types.cpp:58
bool isIntOrFloat() const
Return true if this is an integer (of any signedness) or a float type.
Definition Types.cpp:118
unsigned getIntOrFloatBitWidth() const
Return the bit width of an integer or a float type, assert failure on other types.
Definition Types.cpp:124
type_range getType() const
Location getLoc() const
Return the location of this value.
Definition Value.cpp:24
static WalkResult advance()
Definition WalkResult.h:47
static WalkResult interrupt()
Definition WalkResult.h:46
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
Definition ArithOps.cpp:398
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:732
Include the generated interface declarations.
void populateRaiseWasmMLIRConversionPatterns(TypeConverter &, RewritePatternSet &)
Collect a set of patterns to convert from the Wasm dialect to standard dialects.
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:310
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.