Index: llvm/include/llvm/Analysis/ConstantFolding.h =================================================================== --- llvm/include/llvm/Analysis/ConstantFolding.h +++ llvm/include/llvm/Analysis/ConstantFolding.h @@ -85,6 +85,13 @@ Constant *ConstantFoldBinaryOpOperands(unsigned Opcode, Constant *LHS, Constant *RHS, const DataLayout &DL); +/// Attempt to constant fold a floating point binary operation with the +/// specified operands, applying the denormal handling mod to the operands. If +/// it fails, it returns a constant expression of the specified operands. +Constant *ConstantFoldFPInstOperands(unsigned Opcode, Constant *LHS, + Constant *RHS, const DataLayout &DL, + const Instruction *I); + /// Attempt to constant fold a select instruction with the specified /// operands. The constant result is returned if successful; if not, null is /// returned. Index: llvm/lib/Analysis/ConstantFolding.cpp =================================================================== --- llvm/lib/Analysis/ConstantFolding.cpp +++ llvm/lib/Analysis/ConstantFolding.cpp @@ -1014,8 +1014,24 @@ if (Instruction::isUnaryOp(Opcode)) return ConstantFoldUnaryOpOperand(Opcode, Ops[0], DL); - if (Instruction::isBinaryOp(Opcode)) + if (Instruction::isBinaryOp(Opcode)) { + switch (Opcode) { + default: + break; + case Instruction::FAdd: + case Instruction::FSub: + case Instruction::FMul: + case Instruction::FDiv: + case Instruction::FRem: + // Handle floating point instructions seperately to account for denormals + // TODO: If an constant expression is being folded rather than an + // instruction, denormals will not be flushed/treated as zero + if (const auto *I = dyn_cast(InstOrCE)) { + return ConstantFoldFPInstOperands(Opcode, Ops[0], Ops[1], DL, I); + } + } return ConstantFoldBinaryOpOperands(Opcode, Ops[0], Ops[1], DL); + } if (Instruction::isCast(Opcode)) return ConstantFoldCastOperand(Opcode, Ops[0], DestTy, DL); @@ -1310,6 +1326,63 @@ return ConstantExpr::get(Opcode, LHS, RHS); } +// Check whether a constant is a floating point denormal that should be flushed +// to zero according to the denormal handling mode set in the function +// attributes. If so, return a zero with the correct sign, otherwise return the +// original constant. Inputs and outputs to floating point instructions can have +// their mode set seperately, so the direction is also needed. +Constant *FlushFPConstant(Constant *Operand, const llvm::Function *F, + bool IsOutput) { + if (F == nullptr) + return Operand; + if (auto *CFP = dyn_cast(Operand)) { + const APFloat &APF = CFP->getValueAPF(); + Type *Ty = CFP->getType(); + DenormalMode DenormMode = F->getDenormalMode(Ty->getFltSemantics()); + llvm::DenormalMode::DenormalModeKind Mode = + IsOutput ? DenormMode.Output : DenormMode.Input; + switch (Mode) { + default: + llvm_unreachable("unknown denormal mode"); + return Operand; + case llvm::DenormalMode::IEEE: + return Operand; + case llvm::DenormalMode::PreserveSign: + if (APF.isDenormal()) { + return ConstantFP::get( + Ty->getContext(), + APFloat::getZero(Ty->getFltSemantics(), APF.isNegative())); + } + return Operand; + case llvm::DenormalMode::PositiveZero: + if (APF.isDenormal()) { + return ConstantFP::get(Ty->getContext(), + APFloat::getZero(Ty->getFltSemantics(), false)); + } + return Operand; + } + } + return Operand; +} + +Constant *llvm::ConstantFoldFPInstOperands(unsigned Opcode, Constant *LHS, + Constant *RHS, const DataLayout &DL, + const Instruction *I) { + if (auto *BB = I->getParent()) { + if (auto *F = BB->getParent()) { + if (Instruction::isBinaryOp(Opcode)) { + Constant *Op0 = FlushFPConstant(LHS, F, false); + Constant *Op1 = FlushFPConstant(RHS, F, false); + Constant *C = ConstantFoldBinaryOpOperands(Opcode, Op0, Op1, DL); + return FlushFPConstant(C, F, true); + } + } + } + // If instuction lacks a parent/function and the denormal mode cannot be + // determined, use the default (IEEE). + return ConstantFoldBinaryOpOperands(Opcode, LHS, RHS, DL); +} + Constant *llvm::ConstantFoldCastOperand(unsigned Opcode, Constant *C, Type *DestTy, const DataLayout &DL) { assert(Instruction::isCast(Opcode)); Index: llvm/lib/Analysis/InstructionSimplify.cpp =================================================================== --- llvm/lib/Analysis/InstructionSimplify.cpp +++ llvm/lib/Analysis/InstructionSimplify.cpp @@ -600,8 +600,20 @@ Value *&Op0, Value *&Op1, const SimplifyQuery &Q) { if (auto *CLHS = dyn_cast(Op0)) { - if (auto *CRHS = dyn_cast(Op1)) + if (auto *CRHS = dyn_cast(Op1)) { + switch (Opcode) { + default: + break; + case Instruction::FAdd: + case Instruction::FSub: + case Instruction::FMul: + case Instruction::FDiv: + case Instruction::FRem: + if (Q.CxtI != nullptr) + return ConstantFoldFPInstOperands(Opcode, CLHS, CRHS, Q.DL, Q.CxtI); + } return ConstantFoldBinaryOpOperands(Opcode, CLHS, CRHS, Q.DL); + } // Canonicalize the constant to the RHS if this is a commutative operation. if (Instruction::isCommutative(Opcode)) Index: llvm/test/Transforms/InstCombine/AArch64/constant-fold-fp-denormal.ll =================================================================== --- /dev/null +++ llvm/test/Transforms/InstCombine/AArch64/constant-fold-fp-denormal.ll @@ -0,0 +1,72 @@ +; RUN: opt -S -instcombine < %s | FileCheck %s + +target triple = "aarch64--linux-gnu" + +define float @test_float() #0 { +; CHECK-LABEL: @test_float( +; CHECK-NEXT: ret float 0x3800000000000000 + %mul = fmul float 0x3810000000000000, 5.000000e-01 + ret float %mul +} + +define double @test_double() #0 { +; CHECK-LABEL: @test_double( +; CHECK-NEXT: ret double 0x8000000000000 + %mul = fmul double 0x10000000000000, 5.000000e-01 + ret double %mul +} + +define float @test_float_positive() #1 { +; CHECK-LABEL: @test_float_positive( +; CHECK-NEXT: ret float 0.000000e+00 + %mul = fmul float 0x3810000000000000, 5.000000e-01 + ret float %mul +} + +define double @test_double_positive() #1 { +; CHECK-LABEL: @test_double_positive( +; CHECK-NEXT: ret double 0.000000e+00 + %mul = fmul double 0x10000000000000, 5.000000e-01 + ret double %mul +} + +define float @test_float_preserve() #2 { +; CHECK-LABEL: @test_float_preserve( +; CHECK-NEXT: ret float -0.000000e+00 + %mul = fmul float 0x3810000000000000, -5.000000e-01 + ret float %mul +} + +define double @test_double_preserve() #2 { +; CHECK-LABEL: @test_double_preserve( +; CHECK-NEXT: ret double -0.000000e+00 + %mul = fmul double 0x10000000000000, -5.000000e-01 + ret double %mul +} + +define float @test_float_positive_f32() #3 { +; CHECK-LABEL: @test_float_positive_f32( +; CHECK-NEXT: ret float 0.000000e+00 + %mul = fmul float 0x3810000000000000, 5.000000e-01 + ret float %mul +} + +define double @test_double_positive_f32() #3 { +; CHECK-LABEL: @test_double_positive_f32( +; CHECK-NEXT: ret double 0x8000000000000 + %mul = fmul double 0x10000000000000, 5.000000e-01 + ret double %mul +} + +define double @test_double_input() #4 { +; CHECK-LABEL: @test_double_input( +; CHECK-NEXT: ret double 0.000000e+00 + %mul = fmul double 0x8000000000000, 1.000000e+10 + ret double %mul +} + +attributes #0 = { nounwind "denormal-fp-math"="ieee,ieee" } +attributes #1 = { nounwind "denormal-fp-math"="positive-zero,ieee" } +attributes #2 = { nounwind "denormal-fp-math"="preserve-sign,ieee" } +attributes #3 = { nounwind "denormal-fp-math"="ieee,ieee" "denormal-fp-math-f32"="positive-zero,ieee" } +attributes #4 = { nounwind "denormal-fp-math"="ieee,positive-zero" } Index: llvm/test/Transforms/InstCombine/ARM/constant-fold-fp-denormal.ll =================================================================== --- /dev/null +++ llvm/test/Transforms/InstCombine/ARM/constant-fold-fp-denormal.ll @@ -0,0 +1,72 @@ +; RUN: opt -S -instcombine < %s | FileCheck %s + +target triple = "armv8-arm-none-eabi" + +define float @test_float() #0 { +; CHECK-LABEL: @test_float( +; CHECK-NEXT: ret float 0x3800000000000000 + %mul = fmul float 0x3810000000000000, 5.000000e-01 + ret float %mul +} + +define double @test_double() #0 { +; CHECK-LABEL: @test_double( +; CHECK-NEXT: ret double 0x8000000000000 + %mul = fmul double 0x10000000000000, 5.000000e-01 + ret double %mul +} + +define float @test_float_positive() #1 { +; CHECK-LABEL: @test_float_positive( +; CHECK-NEXT: ret float 0.000000e+00 + %mul = fmul float 0x3810000000000000, 5.000000e-01 + ret float %mul +} + +define double @test_double_positive() #1 { +; CHECK-LABEL: @test_double_positive( +; CHECK-NEXT: ret double 0.000000e+00 + %mul = fmul double 0x10000000000000, 5.000000e-01 + ret double %mul +} + +define float @test_float_preserve() #2 { +; CHECK-LABEL: @test_float_preserve( +; CHECK-NEXT: ret float -0.000000e+00 + %mul = fmul float 0x3810000000000000, -5.000000e-01 + ret float %mul +} + +define double @test_double_preserve() #2 { +; CHECK-LABEL: @test_double_preserve( +; CHECK-NEXT: ret double -0.000000e+00 + %mul = fmul double 0x10000000000000, -5.000000e-01 + ret double %mul +} + +define float @test_float_positive_f32() #3 { +; CHECK-LABEL: @test_float_positive_f32( +; CHECK-NEXT: ret float 0.000000e+00 + %mul = fmul float 0x3810000000000000, 5.000000e-01 + ret float %mul +} + +define double @test_double_positive_f32() #3 { +; CHECK-LABEL: @test_double_positive_f32( +; CHECK-NEXT: ret double 0x8000000000000 + %mul = fmul double 0x10000000000000, 5.000000e-01 + ret double %mul +} + +define double @test_double_input() #4 { +; CHECK-LABEL: @test_double_input( +; CHECK-NEXT: ret double 0.000000e+00 + %mul = fmul double 0x8000000000000, 1.000000e+10 + ret double %mul +} + +attributes #0 = { nounwind "denormal-fp-math"="ieee,ieee" } +attributes #1 = { nounwind "denormal-fp-math"="positive-zero,ieee" } +attributes #2 = { nounwind "denormal-fp-math"="preserve-sign,ieee" } +attributes #3 = { nounwind "denormal-fp-math"="ieee,ieee" "denormal-fp-math-f32"="positive-zero,ieee" } +attributes #4 = { nounwind "denormal-fp-math"="ieee,positive-zero" }