[rv32] Expand `arith.mulsi_extended` before going to LLVM (#12241)
This enables `tosa.apply_scale` to be vectorized, and thus fixes a code
size regression.
Fixes: https://github.com/iree-org/iree/issues/12239
diff --git a/compiler/src/iree/compiler/Codegen/LLVMCPU/ConvertToLLVM.cpp b/compiler/src/iree/compiler/Codegen/LLVMCPU/ConvertToLLVM.cpp
index 1c7aae3..b19f1ca 100644
--- a/compiler/src/iree/compiler/Codegen/LLVMCPU/ConvertToLLVM.cpp
+++ b/compiler/src/iree/compiler/Codegen/LLVMCPU/ConvertToLLVM.cpp
@@ -48,6 +48,9 @@
#include "mlir/Dialect/Vector/IR/VectorOps.h"
#include "mlir/IR/BuiltinAttributes.h"
#include "mlir/IR/BuiltinOps.h"
+#include "mlir/IR/BuiltinTypes.h"
+#include "mlir/IR/Location.h"
+#include "mlir/IR/TypeUtilities.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
@@ -358,6 +361,49 @@
LLVMTypeConverter &typeConverter;
};
+/// The 32-bit RISC-V backend is very sensitive to how extended multiplication
+/// is lowered. This pattern lowers `arith.mulsi_extended` before going to the
+/// LLVM dialect, in a way compatible with that backend, so that we break down
+/// any 64-bit constants that would otherwise prevent the code from being
+/// vectorized.
+class ExpandMulSIExtended : public OpRewritePattern<arith::MulSIExtendedOp> {
+ public:
+ using OpRewritePattern<arith::MulSIExtendedOp>::OpRewritePattern;
+
+ LogicalResult matchAndRewrite(arith::MulSIExtendedOp op,
+ PatternRewriter &rewriter) const override {
+ Type resultType = op.getLhs().getType();
+ if (getElementTypeOrSelf(resultType).getIntOrFloatBitWidth() != 32) {
+ return failure();
+ }
+
+ Location loc = op.getLoc();
+
+ Type wideType = rewriter.getIntegerType(64);
+ // Shift amount necessary to extract the high bits from widened result.
+ Attribute shiftValAttr = rewriter.getI64IntegerAttr(32);
+ if (auto vecTy = resultType.dyn_cast<VectorType>()) {
+ wideType = VectorType::get(vecTy.getShape(), wideType);
+ shiftValAttr = SplatElementsAttr::get(wideType, shiftValAttr);
+ }
+ Value shiftVal = rewriter.create<arith::ConstantOp>(loc, shiftValAttr);
+
+ Value lhsExt = rewriter.create<arith::ExtSIOp>(loc, wideType, op.getLhs());
+ Value rhsExt = rewriter.create<arith::ExtSIOp>(loc, wideType, op.getRhs());
+ Value mulExt =
+ rewriter.create<arith::MulIOp>(loc, wideType, lhsExt, rhsExt);
+ Value low = rewriter.create<arith::MulIOp>(loc, resultType, op.getLhs(),
+ op.getRhs());
+
+ // Produce two 32-bit results.
+ Value highExt = rewriter.create<arith::ShRUIOp>(loc, mulExt, shiftVal);
+ Value high = rewriter.create<arith::TruncIOp>(loc, resultType, highExt);
+
+ rewriter.replaceOp(op, {low, high});
+ return success();
+ }
+};
+
class ConvertToLLVMPass : public ConvertToLLVMBase<ConvertToLLVMPass> {
public:
ConvertToLLVMPass(bool reassociateFpReductions) {
@@ -465,6 +511,12 @@
}
tosa::populateTosaRescaleToArithConversionPatterns(&patterns, use32BitImpl);
+ // Make sure we expand any `arith.mulsi_extended` before going to the LLVM
+ // dialect.
+ if (use32BitImpl) {
+ patterns.add<ExpandMulSIExtended>(patterns.getContext(), /*benefit=*/1024);
+ }
+
populateAffineToStdConversionPatterns(patterns);
populateSCFToControlFlowConversionPatterns(patterns);
cf::populateControlFlowToLLVMConversionPatterns(typeConverter, patterns);
diff --git a/compiler/src/iree/compiler/Codegen/LLVMCPU/test/apply_scale_lowering.mlir b/compiler/src/iree/compiler/Codegen/LLVMCPU/test/apply_scale_lowering.mlir
index c5602fd..a86a5af 100644
--- a/compiler/src/iree/compiler/Codegen/LLVMCPU/test/apply_scale_lowering.mlir
+++ b/compiler/src/iree/compiler/Codegen/LLVMCPU/test/apply_scale_lowering.mlir
@@ -180,8 +180,13 @@
// 32-bit lowering is used with '+zve32x'. Note that the 32-bit lowering
// generates 64-bit mul operations that are decomposed into 32-bit operations by
-// the LLVM backend.
+// the LLVM backend. The backend expects both the low half to be an `llvm.mul` op.
// CHECK-LABEL: llvm.func @apply_scale_zve32x
-// CHECK: %[[MUL:.*]] = llvm.mul %{{.*}}, %{{.*}} : vector<2xi64>
-// CHECK: %[[SHR:.*]] = llvm.lshr %{{.*}}, %{{.*}} : vector<2xi64>
+// CHECK-DAG: %[[RHS:.+]] = llvm.mlir.constant(dense<19689> : vector<2xi32>) : vector<2xi32>
+// CHECK-DAG: %[[RHSEXT:.+]] = llvm.mlir.constant(dense<19689> : vector<2xi64>) : vector<2xi64>
+// CHECK: %[[LHS:.+]] = llvm.load %{{.+}} {alignment = 4 : i64} : !llvm.ptr<vector<2xi32>>
+// CHECK: %[[LHSEXT:.+]] = llvm.sext %[[LHS]] : vector<2xi32> to vector<2xi64>
+// CHECK: %[[MULEXT:.*]] = llvm.mul %[[LHSEXT]], %[[RHSEXT]] : vector<2xi64>
+// CHECK: %[[MULLOW:.*]] = llvm.mul %[[LHS]], %[[RHS]] : vector<2xi32>
+// CHECK: %[[SHR:.*]] = llvm.lshr %[[MULEXT]], %{{.*}} : vector<2xi64>
// CHECK-NEXT: llvm.trunc %[[SHR]] : vector<2xi64> to vector<2xi32>