550 lines · cpp
1//===- ArithToAPFloat.cpp - Arithmetic to APFloat 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-exception6//7//===----------------------------------------------------------------------===//8 9#include "mlir/Conversion/ArithToAPFloat/ArithToAPFloat.h"10 11#include "mlir/Dialect/Arith/IR/Arith.h"12#include "mlir/Dialect/Arith/Transforms/Passes.h"13#include "mlir/Dialect/Func/IR/FuncOps.h"14#include "mlir/Dialect/Func/Utils/Utils.h"15#include "mlir/IR/PatternMatch.h"16#include "mlir/IR/Verifier.h"17#include "mlir/Transforms/WalkPatternRewriteDriver.h"18 19namespace mlir {20#define GEN_PASS_DEF_ARITHTOAPFLOATCONVERSIONPASS21#include "mlir/Conversion/Passes.h.inc"22} // namespace mlir23 24using namespace mlir;25using namespace mlir::func;26 27static FuncOp createFnDecl(OpBuilder &b, SymbolOpInterface symTable,28 StringRef name, FunctionType funcT, bool setPrivate,29 SymbolTableCollection *symbolTables = nullptr) {30 OpBuilder::InsertionGuard g(b);31 assert(!symTable->getRegion(0).empty() && "expected non-empty region");32 b.setInsertionPointToStart(&symTable->getRegion(0).front());33 FuncOp funcOp = FuncOp::create(b, symTable->getLoc(), name, funcT);34 if (setPrivate)35 funcOp.setPrivate();36 if (symbolTables) {37 SymbolTable &symbolTable = symbolTables->getSymbolTable(symTable);38 symbolTable.insert(funcOp, symTable->getRegion(0).front().begin());39 }40 return funcOp;41}42 43/// Helper function to look up or create the symbol for a runtime library44/// function with the given parameter types. Returns an int64_t, unless a45/// different result type is specified.46static FailureOr<FuncOp>47lookupOrCreateApFloatFn(OpBuilder &b, SymbolOpInterface symTable,48 StringRef name, TypeRange paramTypes,49 SymbolTableCollection *symbolTables = nullptr,50 Type resultType = {}) {51 if (!resultType)52 resultType = IntegerType::get(symTable->getContext(), 64);53 std::string funcName = (llvm::Twine("_mlir_apfloat_") + name).str();54 auto funcT = FunctionType::get(b.getContext(), paramTypes, {resultType});55 FailureOr<FuncOp> func =56 lookupFnDecl(symTable, funcName, funcT, symbolTables);57 // Failed due to type mismatch.58 if (failed(func))59 return func;60 // Successfully matched existing decl.61 if (*func)62 return *func;63 64 return createFnDecl(b, symTable, funcName, funcT,65 /*setPrivate=*/true, symbolTables);66}67 68/// Helper function to look up or create the symbol for a runtime library69/// function for a binary arithmetic operation.70///71/// Parameter 1: APFloat semantics72/// Parameter 2: Left-hand side operand73/// Parameter 3: Right-hand side operand74///75/// This function will return a failure if the function is found but has an76/// unexpected signature.77///78static FailureOr<FuncOp>79lookupOrCreateBinaryFn(OpBuilder &b, SymbolOpInterface symTable, StringRef name,80 SymbolTableCollection *symbolTables = nullptr) {81 auto i32Type = IntegerType::get(symTable->getContext(), 32);82 auto i64Type = IntegerType::get(symTable->getContext(), 64);83 return lookupOrCreateApFloatFn(b, symTable, name, {i32Type, i64Type, i64Type},84 symbolTables);85}86 87static Value getSemanticsValue(OpBuilder &b, Location loc, FloatType floatTy) {88 int32_t sem = llvm::APFloatBase::SemanticsToEnum(floatTy.getFloatSemantics());89 return arith::ConstantOp::create(b, loc, b.getI32Type(),90 b.getIntegerAttr(b.getI32Type(), sem));91}92 93/// Rewrite a binary arithmetic operation to an APFloat function call.94template <typename OpTy>95struct BinaryArithOpToAPFloatConversion final : OpRewritePattern<OpTy> {96 BinaryArithOpToAPFloatConversion(MLIRContext *context,97 const char *APFloatName,98 SymbolOpInterface symTable,99 PatternBenefit benefit = 1)100 : OpRewritePattern<OpTy>(context, benefit), symTable(symTable),101 APFloatName(APFloatName) {};102 103 LogicalResult matchAndRewrite(OpTy op,104 PatternRewriter &rewriter) const override {105 // Get APFloat function from runtime library.106 FailureOr<FuncOp> fn =107 lookupOrCreateBinaryFn(rewriter, symTable, APFloatName);108 if (failed(fn))109 return fn;110 111 rewriter.setInsertionPoint(op);112 // Cast operands to 64-bit integers.113 Location loc = op.getLoc();114 auto floatTy = cast<FloatType>(op.getType());115 auto intWType = rewriter.getIntegerType(floatTy.getWidth());116 auto int64Type = rewriter.getI64Type();117 Value lhsBits = arith::ExtUIOp::create(118 rewriter, loc, int64Type,119 arith::BitcastOp::create(rewriter, loc, intWType, op.getLhs()));120 Value rhsBits = arith::ExtUIOp::create(121 rewriter, loc, int64Type,122 arith::BitcastOp::create(rewriter, loc, intWType, op.getRhs()));123 124 // Call APFloat function.125 Value semValue = getSemanticsValue(rewriter, loc, floatTy);126 SmallVector<Value> params = {semValue, lhsBits, rhsBits};127 auto resultOp =128 func::CallOp::create(rewriter, loc, TypeRange(rewriter.getI64Type()),129 SymbolRefAttr::get(*fn), params);130 131 // Truncate result to the original width.132 Value truncatedBits = arith::TruncIOp::create(rewriter, loc, intWType,133 resultOp->getResult(0));134 rewriter.replaceOp(135 op, arith::BitcastOp::create(rewriter, loc, floatTy, truncatedBits));136 return success();137 }138 139 SymbolOpInterface symTable;140 const char *APFloatName;141};142 143template <typename OpTy>144struct FpToFpConversion final : OpRewritePattern<OpTy> {145 FpToFpConversion(MLIRContext *context, SymbolOpInterface symTable,146 PatternBenefit benefit = 1)147 : OpRewritePattern<OpTy>(context, benefit), symTable(symTable) {}148 149 LogicalResult matchAndRewrite(OpTy op,150 PatternRewriter &rewriter) const override {151 // Get APFloat function from runtime library.152 auto i32Type = IntegerType::get(symTable->getContext(), 32);153 auto i64Type = IntegerType::get(symTable->getContext(), 64);154 FailureOr<FuncOp> fn = lookupOrCreateApFloatFn(155 rewriter, symTable, "convert", {i32Type, i32Type, i64Type});156 if (failed(fn))157 return fn;158 159 rewriter.setInsertionPoint(op);160 // Cast operands to 64-bit integers.161 Location loc = op.getLoc();162 auto inFloatTy = cast<FloatType>(op.getOperand().getType());163 auto inIntWType = rewriter.getIntegerType(inFloatTy.getWidth());164 Value operandBits = arith::ExtUIOp::create(165 rewriter, loc, i64Type,166 arith::BitcastOp::create(rewriter, loc, inIntWType, op.getOperand()));167 168 // Call APFloat function.169 Value inSemValue = getSemanticsValue(rewriter, loc, inFloatTy);170 auto outFloatTy = cast<FloatType>(op.getType());171 Value outSemValue = getSemanticsValue(rewriter, loc, outFloatTy);172 std::array<Value, 3> params = {inSemValue, outSemValue, operandBits};173 auto resultOp =174 func::CallOp::create(rewriter, loc, TypeRange(rewriter.getI64Type()),175 SymbolRefAttr::get(*fn), params);176 177 // Truncate result to the original width.178 auto outIntWType = rewriter.getIntegerType(outFloatTy.getWidth());179 Value truncatedBits = arith::TruncIOp::create(rewriter, loc, outIntWType,180 resultOp->getResult(0));181 rewriter.replaceOp(182 op, arith::BitcastOp::create(rewriter, loc, outFloatTy, truncatedBits));183 return success();184 }185 186 SymbolOpInterface symTable;187};188 189template <typename OpTy>190struct FpToIntConversion final : OpRewritePattern<OpTy> {191 FpToIntConversion(MLIRContext *context, SymbolOpInterface symTable,192 bool isUnsigned, PatternBenefit benefit = 1)193 : OpRewritePattern<OpTy>(context, benefit), symTable(symTable),194 isUnsigned(isUnsigned) {}195 196 LogicalResult matchAndRewrite(OpTy op,197 PatternRewriter &rewriter) const override {198 if (op.getType().getIntOrFloatBitWidth() > 64)199 return rewriter.notifyMatchFailure(200 op, "result type > 64 bits is not supported");201 202 // Get APFloat function from runtime library.203 auto i1Type = IntegerType::get(symTable->getContext(), 1);204 auto i32Type = IntegerType::get(symTable->getContext(), 32);205 auto i64Type = IntegerType::get(symTable->getContext(), 64);206 FailureOr<FuncOp> fn =207 lookupOrCreateApFloatFn(rewriter, symTable, "convert_to_int",208 {i32Type, i32Type, i1Type, i64Type});209 if (failed(fn))210 return fn;211 212 rewriter.setInsertionPoint(op);213 // Cast operands to 64-bit integers.214 Location loc = op.getLoc();215 auto inFloatTy = cast<FloatType>(op.getOperand().getType());216 auto inIntWType = rewriter.getIntegerType(inFloatTy.getWidth());217 Value operandBits = arith::ExtUIOp::create(218 rewriter, loc, i64Type,219 arith::BitcastOp::create(rewriter, loc, inIntWType, op.getOperand()));220 221 // Call APFloat function.222 Value inSemValue = getSemanticsValue(rewriter, loc, inFloatTy);223 auto outIntTy = cast<IntegerType>(op.getType());224 Value outWidthValue = arith::ConstantOp::create(225 rewriter, loc, i32Type,226 rewriter.getIntegerAttr(i32Type, outIntTy.getWidth()));227 Value isUnsignedValue = arith::ConstantOp::create(228 rewriter, loc, i1Type, rewriter.getIntegerAttr(i1Type, isUnsigned));229 SmallVector<Value> params = {inSemValue, outWidthValue, isUnsignedValue,230 operandBits};231 auto resultOp =232 func::CallOp::create(rewriter, loc, TypeRange(rewriter.getI64Type()),233 SymbolRefAttr::get(*fn), params);234 235 // Truncate result to the original width.236 Value truncatedBits = arith::TruncIOp::create(rewriter, loc, outIntTy,237 resultOp->getResult(0));238 rewriter.replaceOp(op, truncatedBits);239 return success();240 }241 242 SymbolOpInterface symTable;243 bool isUnsigned;244};245 246template <typename OpTy>247struct IntToFpConversion final : OpRewritePattern<OpTy> {248 IntToFpConversion(MLIRContext *context, SymbolOpInterface symTable,249 bool isUnsigned, PatternBenefit benefit = 1)250 : OpRewritePattern<OpTy>(context, benefit), symTable(symTable),251 isUnsigned(isUnsigned) {}252 253 LogicalResult matchAndRewrite(OpTy op,254 PatternRewriter &rewriter) const override {255 Location loc = op.getLoc();256 if (op.getIn().getType().getIntOrFloatBitWidth() > 64) {257 return rewriter.notifyMatchFailure(258 loc, "integer bitwidth > 64 is not supported");259 }260 261 // Get APFloat function from runtime library.262 auto i1Type = IntegerType::get(symTable->getContext(), 1);263 auto i32Type = IntegerType::get(symTable->getContext(), 32);264 auto i64Type = IntegerType::get(symTable->getContext(), 64);265 FailureOr<FuncOp> fn =266 lookupOrCreateApFloatFn(rewriter, symTable, "convert_from_int",267 {i32Type, i32Type, i1Type, i64Type});268 if (failed(fn))269 return fn;270 271 rewriter.setInsertionPoint(op);272 // Cast operands to 64-bit integers.273 auto inIntTy = cast<IntegerType>(op.getOperand().getType());274 Value operandBits = op.getOperand();275 if (operandBits.getType().getIntOrFloatBitWidth() < 64) {276 if (isUnsigned) {277 operandBits =278 arith::ExtUIOp::create(rewriter, loc, i64Type, operandBits);279 } else {280 operandBits =281 arith::ExtSIOp::create(rewriter, loc, i64Type, operandBits);282 }283 }284 285 // Call APFloat function.286 auto outFloatTy = cast<FloatType>(op.getType());287 Value outSemValue = getSemanticsValue(rewriter, loc, outFloatTy);288 Value inWidthValue = arith::ConstantOp::create(289 rewriter, loc, i32Type,290 rewriter.getIntegerAttr(i32Type, inIntTy.getWidth()));291 Value isUnsignedValue = arith::ConstantOp::create(292 rewriter, loc, i1Type, rewriter.getIntegerAttr(i1Type, isUnsigned));293 SmallVector<Value> params = {outSemValue, inWidthValue, isUnsignedValue,294 operandBits};295 auto resultOp =296 func::CallOp::create(rewriter, loc, TypeRange(rewriter.getI64Type()),297 SymbolRefAttr::get(*fn), params);298 299 // Truncate result to the original width.300 auto outIntWType = rewriter.getIntegerType(outFloatTy.getWidth());301 Value truncatedBits = arith::TruncIOp::create(rewriter, loc, outIntWType,302 resultOp->getResult(0));303 Value result =304 arith::BitcastOp::create(rewriter, loc, outFloatTy, truncatedBits);305 rewriter.replaceOp(op, result);306 return success();307 }308 309 SymbolOpInterface symTable;310 bool isUnsigned;311};312 313struct CmpFOpToAPFloatConversion final : OpRewritePattern<arith::CmpFOp> {314 CmpFOpToAPFloatConversion(MLIRContext *context, SymbolOpInterface symTable,315 PatternBenefit benefit = 1)316 : OpRewritePattern<arith::CmpFOp>(context, benefit), symTable(symTable) {}317 318 LogicalResult matchAndRewrite(arith::CmpFOp op,319 PatternRewriter &rewriter) const override {320 // Get APFloat function from runtime library.321 auto i1Type = IntegerType::get(symTable->getContext(), 1);322 auto i8Type = IntegerType::get(symTable->getContext(), 8);323 auto i32Type = IntegerType::get(symTable->getContext(), 32);324 auto i64Type = IntegerType::get(symTable->getContext(), 64);325 FailureOr<FuncOp> fn =326 lookupOrCreateApFloatFn(rewriter, symTable, "compare",327 {i32Type, i64Type, i64Type}, nullptr, i8Type);328 if (failed(fn))329 return fn;330 331 // Cast operands to 64-bit integers.332 rewriter.setInsertionPoint(op);333 Location loc = op.getLoc();334 auto floatTy = cast<FloatType>(op.getLhs().getType());335 auto intWType = rewriter.getIntegerType(floatTy.getWidth());336 Value lhsBits = arith::ExtUIOp::create(337 rewriter, loc, i64Type,338 arith::BitcastOp::create(rewriter, loc, intWType, op.getLhs()));339 Value rhsBits = arith::ExtUIOp::create(340 rewriter, loc, i64Type,341 arith::BitcastOp::create(rewriter, loc, intWType, op.getRhs()));342 343 // Call APFloat function.344 Value semValue = getSemanticsValue(rewriter, loc, floatTy);345 SmallVector<Value> params = {semValue, lhsBits, rhsBits};346 Value comparisonResult =347 func::CallOp::create(rewriter, loc, TypeRange(i8Type),348 SymbolRefAttr::get(*fn), params)349 ->getResult(0);350 351 // Generate an i1 SSA value that is "true" if the comparison result matches352 // the given `val`.353 auto checkResult = [&](llvm::APFloat::cmpResult val) {354 return arith::CmpIOp::create(355 rewriter, loc, arith::CmpIPredicate::eq, comparisonResult,356 arith::ConstantOp::create(357 rewriter, loc, i8Type,358 rewriter.getIntegerAttr(i8Type, static_cast<int8_t>(val)))359 .getResult());360 };361 // Generate an i1 SSA value that is "true" if the comparison result matches362 // any of the given `vals`.363 std::function<Value(ArrayRef<llvm::APFloat::cmpResult>)> checkResults =364 [&](ArrayRef<llvm::APFloat::cmpResult> vals) {365 Value first = checkResult(vals.front());366 if (vals.size() == 1)367 return first;368 Value rest = checkResults(vals.drop_front());369 return arith::OrIOp::create(rewriter, loc, first, rest).getResult();370 };371 372 // This switch-case statement was taken from arith::applyCmpPredicate.373 Value result;374 switch (op.getPredicate()) {375 case arith::CmpFPredicate::AlwaysFalse:376 result = arith::ConstantOp::create(rewriter, loc, i1Type,377 rewriter.getIntegerAttr(i1Type, 0))378 .getResult();379 break;380 case arith::CmpFPredicate::OEQ:381 result = checkResult(llvm::APFloat::cmpEqual);382 break;383 case arith::CmpFPredicate::OGT:384 result = checkResult(llvm::APFloat::cmpGreaterThan);385 break;386 case arith::CmpFPredicate::OGE:387 result = checkResults(388 {llvm::APFloat::cmpGreaterThan, llvm::APFloat::cmpEqual});389 break;390 case arith::CmpFPredicate::OLT:391 result = checkResult(llvm::APFloat::cmpLessThan);392 break;393 case arith::CmpFPredicate::OLE:394 result =395 checkResults({llvm::APFloat::cmpLessThan, llvm::APFloat::cmpEqual});396 break;397 case arith::CmpFPredicate::ONE:398 // Not cmpUnordered and not cmpUnordered.399 result = checkResults(400 {llvm::APFloat::cmpLessThan, llvm::APFloat::cmpGreaterThan});401 break;402 case arith::CmpFPredicate::ORD:403 // Not cmpUnordered.404 result = checkResults({llvm::APFloat::cmpLessThan,405 llvm::APFloat::cmpGreaterThan,406 llvm::APFloat::cmpEqual});407 break;408 case arith::CmpFPredicate::UEQ:409 result =410 checkResults({llvm::APFloat::cmpUnordered, llvm::APFloat::cmpEqual});411 break;412 case arith::CmpFPredicate::UGT:413 result = checkResults(414 {llvm::APFloat::cmpUnordered, llvm::APFloat::cmpGreaterThan});415 break;416 case arith::CmpFPredicate::UGE:417 result = checkResults({llvm::APFloat::cmpUnordered,418 llvm::APFloat::cmpGreaterThan,419 llvm::APFloat::cmpEqual});420 break;421 case arith::CmpFPredicate::ULT:422 result = checkResults(423 {llvm::APFloat::cmpUnordered, llvm::APFloat::cmpLessThan});424 break;425 case arith::CmpFPredicate::ULE:426 result =427 checkResults({llvm::APFloat::cmpUnordered, llvm::APFloat::cmpLessThan,428 llvm::APFloat::cmpEqual});429 break;430 case arith::CmpFPredicate::UNE:431 // Not cmpEqual.432 result = checkResults({llvm::APFloat::cmpLessThan,433 llvm::APFloat::cmpGreaterThan,434 llvm::APFloat::cmpUnordered});435 break;436 case arith::CmpFPredicate::UNO:437 result = checkResult(llvm::APFloat::cmpUnordered);438 break;439 case arith::CmpFPredicate::AlwaysTrue:440 result = arith::ConstantOp::create(rewriter, loc, i1Type,441 rewriter.getIntegerAttr(i1Type, 1))442 .getResult();443 break;444 }445 rewriter.replaceOp(op, result);446 return success();447 }448 449 SymbolOpInterface symTable;450};451 452struct NegFOpToAPFloatConversion final : OpRewritePattern<arith::NegFOp> {453 NegFOpToAPFloatConversion(MLIRContext *context, SymbolOpInterface symTable,454 PatternBenefit benefit = 1)455 : OpRewritePattern<arith::NegFOp>(context, benefit), symTable(symTable) {}456 457 LogicalResult matchAndRewrite(arith::NegFOp op,458 PatternRewriter &rewriter) const override {459 // Get APFloat function from runtime library.460 auto i32Type = IntegerType::get(symTable->getContext(), 32);461 auto i64Type = IntegerType::get(symTable->getContext(), 64);462 FailureOr<FuncOp> fn =463 lookupOrCreateApFloatFn(rewriter, symTable, "neg", {i32Type, i64Type});464 if (failed(fn))465 return fn;466 467 // Cast operand to 64-bit integer.468 rewriter.setInsertionPoint(op);469 Location loc = op.getLoc();470 auto floatTy = cast<FloatType>(op.getOperand().getType());471 auto intWType = rewriter.getIntegerType(floatTy.getWidth());472 Value operandBits = arith::ExtUIOp::create(473 rewriter, loc, i64Type, arith::BitcastOp::create(rewriter, loc, intWType, op.getOperand()));474 475 // Call APFloat function.476 Value semValue = getSemanticsValue(rewriter, loc, floatTy);477 SmallVector<Value> params = {semValue, operandBits};478 Value negatedBits =479 func::CallOp::create(rewriter, loc, TypeRange(i64Type),480 SymbolRefAttr::get(*fn), params)481 ->getResult(0);482 483 // Truncate result to the original width.484 Value truncatedBits = arith::TruncIOp::create(rewriter, loc, intWType,485 negatedBits);486 Value result =487 arith::BitcastOp::create(rewriter, loc, floatTy, truncatedBits);488 rewriter.replaceOp(op, result);489 return success();490 }491 492 SymbolOpInterface symTable;493};494 495namespace {496struct ArithToAPFloatConversionPass final497 : impl::ArithToAPFloatConversionPassBase<ArithToAPFloatConversionPass> {498 using Base::Base;499 500 void runOnOperation() override;501};502 503void ArithToAPFloatConversionPass::runOnOperation() {504 MLIRContext *context = &getContext();505 RewritePatternSet patterns(context);506 patterns.add<BinaryArithOpToAPFloatConversion<arith::AddFOp>>(context, "add",507 getOperation());508 patterns.add<BinaryArithOpToAPFloatConversion<arith::SubFOp>>(509 context, "subtract", getOperation());510 patterns.add<BinaryArithOpToAPFloatConversion<arith::MulFOp>>(511 context, "multiply", getOperation());512 patterns.add<BinaryArithOpToAPFloatConversion<arith::DivFOp>>(513 context, "divide", getOperation());514 patterns.add<BinaryArithOpToAPFloatConversion<arith::RemFOp>>(515 context, "remainder", getOperation());516 patterns.add<BinaryArithOpToAPFloatConversion<arith::MinNumFOp>>(517 context, "minnum", getOperation());518 patterns.add<BinaryArithOpToAPFloatConversion<arith::MaxNumFOp>>(519 context, "maxnum", getOperation());520 patterns.add<BinaryArithOpToAPFloatConversion<arith::MinimumFOp>>(521 context, "minimum", getOperation());522 patterns.add<BinaryArithOpToAPFloatConversion<arith::MaximumFOp>>(523 context, "maximum", getOperation());524 patterns525 .add<FpToFpConversion<arith::ExtFOp>, FpToFpConversion<arith::TruncFOp>,526 CmpFOpToAPFloatConversion, NegFOpToAPFloatConversion>(527 context, getOperation());528 patterns.add<FpToIntConversion<arith::FPToSIOp>>(context, getOperation(),529 /*isUnsigned=*/false);530 patterns.add<FpToIntConversion<arith::FPToUIOp>>(context, getOperation(),531 /*isUnsigned=*/true);532 patterns.add<IntToFpConversion<arith::SIToFPOp>>(context, getOperation(),533 /*isUnsigned=*/false);534 patterns.add<IntToFpConversion<arith::UIToFPOp>>(context, getOperation(),535 /*isUnsigned=*/true);536 LogicalResult result = success();537 ScopedDiagnosticHandler scopedHandler(context, [&result](Diagnostic &diag) {538 if (diag.getSeverity() == DiagnosticSeverity::Error) {539 result = failure();540 }541 // NB: if you don't return failure, no other diag handlers will fire (see542 // mlir/lib/IR/Diagnostics.cpp:DiagnosticEngineImpl::emit).543 return failure();544 });545 walkAndApplyPatterns(getOperation(), std::move(patterns));546 if (failed(result))547 return signalPassFailure();548}549} // namespace550