//===------ NVPTXIRPeephole.cpp - NVPTX IR Peephole --------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // This file implements IR-level peephole optimizations. These transformations // run late in the NVPTX IR pass pipeline just before the instruction selection. // // Currently, it implements the following transformation(s): // 1. FMA folding (float/double types): // Transforms FMUL+FADD/FSUB sequences into FMA intrinsics when the // 'contract' fast-math flag is present. Supported patterns: // - fadd(fmul(a, b), c) => fma(a, b, c) // - fadd(c, fmul(a, b)) => fma(a, b, c) // - fadd(fmul(a, b), fmul(c, d)) => fma(a, b, fmul(c, d)) // - fsub(fmul(a, b), c) => fma(a, b, fneg(c)) // - fsub(a, fmul(b, c)) => fma(fneg(b), c, a) // - fsub(fmul(a, b), fmul(c, d)) => fma(a, b, fneg(fmul(c, d))) // //===----------------------------------------------------------------------===// #include "NVPTXUtilities.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/InstIterator.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/Intrinsics.h" #define DEBUG_TYPE "nvptx-ir-peephole" using namespace llvm; static bool tryFoldBinaryFMul(BinaryOperator *BI) { Value *Op0 = BI->getOperand(0); Value *Op1 = BI->getOperand(1); auto *FMul0 = dyn_cast(Op0); auto *FMul1 = dyn_cast(Op1); BinaryOperator *FMul = nullptr; Value *OtherOperand = nullptr; bool IsFirstOperand = false; // Either Op0 or Op1 should be a valid FMul if (FMul0 && FMul0->getOpcode() == Instruction::FMul && FMul0->hasOneUse() && FMul0->hasAllowContract()) { FMul = FMul0; OtherOperand = Op1; IsFirstOperand = true; } else if (FMul1 && FMul1->getOpcode() == Instruction::FMul && FMul1->hasOneUse() && FMul1->hasAllowContract()) { FMul = FMul1; OtherOperand = Op0; IsFirstOperand = false; } else { return false; } bool IsFSub = BI->getOpcode() == Instruction::FSub; LLVM_DEBUG({ const char *OpName = IsFSub ? "FSub" : "FAdd"; dbgs() << "Found " << OpName << " with FMul (single use) as " << (IsFirstOperand ? "first" : "second") << " operand: " << *BI << "\n"; }); Value *MulOp0 = FMul->getOperand(0); Value *MulOp1 = FMul->getOperand(1); IRBuilder<> Builder(BI); Value *FMA = nullptr; if (!IsFSub) { // fadd(fmul(a, b), c) => fma(a, b, c) // fadd(c, fmul(a, b)) => fma(a, b, c) FMA = Builder.CreateIntrinsic(Intrinsic::fma, {BI->getType()}, {MulOp0, MulOp1, OtherOperand}); } else { if (IsFirstOperand) { // fsub(fmul(a, b), c) => fma(a, b, fneg(c)) Value *NegOtherOp = Builder.CreateFNegFMF(OtherOperand, BI->getFastMathFlags()); FMA = Builder.CreateIntrinsic(Intrinsic::fma, {BI->getType()}, {MulOp0, MulOp1, NegOtherOp}); } else { // fsub(a, fmul(b, c)) => fma(fneg(b), c, a) Value *NegMulOp0 = Builder.CreateFNegFMF(MulOp0, FMul->getFastMathFlags()); FMA = Builder.CreateIntrinsic(Intrinsic::fma, {BI->getType()}, {NegMulOp0, MulOp1, OtherOperand}); } } // Combine fast-math flags from the original instructions auto *FMAInst = cast(FMA); FastMathFlags BinaryFMF = BI->getFastMathFlags(); FastMathFlags FMulFMF = FMul->getFastMathFlags(); FastMathFlags NewFMF = FastMathFlags::intersectRewrite(BinaryFMF, FMulFMF) | FastMathFlags::unionValue(BinaryFMF, FMulFMF); FMAInst->setFastMathFlags(NewFMF); LLVM_DEBUG({ const char *OpName = IsFSub ? "FSub" : "FAdd"; dbgs() << "Replacing " << OpName << " with FMA: " << *FMA << "\n"; }); BI->replaceAllUsesWith(FMA); BI->eraseFromParent(); FMul->eraseFromParent(); return true; } static bool foldFMA(Function &F) { bool Changed = false; // Iterate and process float/double FAdd/FSub instructions with allow-contract for (auto &I : llvm::make_early_inc_range(instructions(F))) { if (auto *BI = dyn_cast(&I)) { // Only FAdd and FSub are supported. if (BI->getOpcode() != Instruction::FAdd && BI->getOpcode() != Instruction::FSub) continue; // At minimum, the instruction should have allow-contract. if (!BI->hasAllowContract()) continue; // Only float and double are supported. if (!BI->getType()->isFloatTy() && !BI->getType()->isDoubleTy()) continue; if (tryFoldBinaryFMul(BI)) Changed = true; } } return Changed; } namespace { struct NVPTXIRPeephole : public FunctionPass { static char ID; NVPTXIRPeephole() : FunctionPass(ID) {} bool runOnFunction(Function &F) override; }; } // namespace char NVPTXIRPeephole::ID = 0; INITIALIZE_PASS(NVPTXIRPeephole, "nvptx-ir-peephole", "NVPTX IR Peephole", false, false) bool NVPTXIRPeephole::runOnFunction(Function &F) { return foldFMA(F); } FunctionPass *llvm::createNVPTXIRPeepholePass() { return new NVPTXIRPeephole(); } PreservedAnalyses NVPTXIRPeepholePass::run(Function &F, FunctionAnalysisManager &) { if (!foldFMA(F)) return PreservedAnalyses::all(); PreservedAnalyses PA; PA.preserveSet(); return PA; }