-
Notifications
You must be signed in to change notification settings - Fork 14.9k
[CIR] Upstream DivOp for ComplexType #153796
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
@llvm/pr-subscribers-clang @llvm/pr-subscribers-clangir Author: Amr Hesham (AmrDeveloper) ChangesThis change adds support for the division operation between two complex types Issue: #141365 Patch is 45.72 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/153796.diff 5 Files Affected:
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index a77e9199cdc96..4ccc5b1f24a5d 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -2966,7 +2966,7 @@ def CIR_ComplexSubOp : CIR_Op<"complex.sub", [
}
//===----------------------------------------------------------------------===//
-// ComplexMulOp
+// ComplexMulOp & ComplexDivOp
//===----------------------------------------------------------------------===//
def CIR_ComplexRangeKind : CIR_I32EnumAttr<
@@ -3013,6 +3013,44 @@ def CIR_ComplexMulOp : CIR_Op<"complex.mul", [
}];
}
+def CIR_ComplexDivOp : CIR_Op<"complex.div", [
+ Pure, SameOperandsAndResultType
+]> {
+ let summary = "Complex division";
+ let description = [{
+ The `cir.complex.div` operation takes two complex numbers and returns
+ their division.
+
+ Range is used to select the implementation used when the operation
+ is lowered to the LLVM dialect. For division, 'improved' and
+ 'promoted' are all handled equivalently, producing the
+ Smith's algorithms for Complex division. If 'full' is used,
+ a runtime-library function is called if one of the intermediate
+ calculations produced a NaN value, and for 'basic' algebraic formula with
+ no special handling for NaN value will be used.
+
+ Example:
+
+ ```mlir
+ %2 = cir.complex.div %0, %1 range(basic) : !cir.complex<!cir.float>
+ %2 = cir.complex.div %0, %1 range(full) : !cir.complex<!cir.float>
+ ```
+ }];
+
+ let arguments = (ins
+ CIR_ComplexType:$lhs,
+ CIR_ComplexType:$rhs,
+ CIR_ComplexRangeKind:$range,
+ UnitAttr:$promoted
+ );
+
+ let results = (outs CIR_ComplexType:$result);
+
+ let assemblyFormat = [{
+ $lhs `,` $rhs `range` `(` $range `)` `:` qualified(type($result)) attr-dict
+ }];
+}
+
//===----------------------------------------------------------------------===//
// Bit Manipulation Operations
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprComplex.cpp b/clang/lib/CIR/CodeGen/CIRGenExprComplex.cpp
index 85cd0282ffc2a..b1afab398d7f4 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprComplex.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprComplex.cpp
@@ -10,6 +10,7 @@ namespace {
class ComplexExprEmitter : public StmtVisitor<ComplexExprEmitter, mlir::Value> {
CIRGenFunction &cgf;
CIRGenBuilderTy &builder;
+ bool fpHasBeenPromoted = false;
public:
explicit ComplexExprEmitter(CIRGenFunction &cgf)
@@ -128,6 +129,35 @@ class ComplexExprEmitter : public StmtVisitor<ComplexExprEmitter, mlir::Value> {
mlir::Value emitBinAdd(const BinOpInfo &op);
mlir::Value emitBinSub(const BinOpInfo &op);
mlir::Value emitBinMul(const BinOpInfo &op);
+ mlir::Value emitBinDiv(const BinOpInfo &op);
+
+ QualType higherPrecisionTypeForComplexArithmetic(QualType elementType,
+ bool isDivOpCode) {
+ ASTContext &astContext = cgf.getContext();
+ const QualType higherElementType =
+ astContext.GetHigherPrecisionFPType(elementType);
+ const llvm::fltSemantics &elementTypeSemantics =
+ astContext.getFloatTypeSemantics(elementType);
+ const llvm::fltSemantics &higherElementTypeSemantics =
+ astContext.getFloatTypeSemantics(higherElementType);
+
+ // Check that the promoted type can handle the intermediate values without
+ // overflowing. This can be interpreted as:
+ // (SmallerType.LargestFiniteVal * SmallerType.LargestFiniteVal) * 2 <=
+ // LargerType.LargestFiniteVal.
+ // In terms of exponent it gives this formula:
+ // (SmallerType.LargestFiniteVal * SmallerType.LargestFiniteVal
+ // doubles the exponent of SmallerType.LargestFiniteVal)
+ if (llvm::APFloat::semanticsMaxExponent(elementTypeSemantics) * 2 + 1 <=
+ llvm::APFloat::semanticsMaxExponent(higherElementTypeSemantics)) {
+ fpHasBeenPromoted = true;
+ return astContext.getComplexType(higherElementType);
+ }
+
+ // The intermediate values can't be represented in the promoted type
+ // without overflowing.
+ return QualType();
+ }
QualType getPromotionType(QualType ty, bool isDivOpCode = false) {
if (auto *complexTy = ty->getAs<ComplexType>()) {
@@ -135,8 +165,7 @@ class ComplexExprEmitter : public StmtVisitor<ComplexExprEmitter, mlir::Value> {
if (isDivOpCode && elementTy->isFloatingType() &&
cgf.getLangOpts().getComplexRange() ==
LangOptions::ComplexRangeKind::CX_Promoted) {
- cgf.cgm.errorNYI("HigherPrecisionTypeForComplexArithmetic");
- return QualType();
+ return higherPrecisionTypeForComplexArithmetic(elementTy, isDivOpCode);
}
if (elementTy.UseExcessPrecision(cgf.getContext()))
@@ -154,13 +183,14 @@ class ComplexExprEmitter : public StmtVisitor<ComplexExprEmitter, mlir::Value> {
e->getType(), e->getOpcode() == BinaryOperatorKind::BO_Div); \
mlir::Value result = emitBin##OP(emitBinOps(e, promotionTy)); \
if (!promotionTy.isNull()) \
- cgf.cgm.errorNYI("Binop emitUnPromotedValue"); \
+ result = cgf.emitUnPromotedValue(result, e->getType()); \
return result; \
}
HANDLEBINOP(Add)
HANDLEBINOP(Sub)
HANDLEBINOP(Mul)
+ HANDLEBINOP(Div)
#undef HANDLEBINOP
// Compound assignments.
@@ -858,6 +888,22 @@ mlir::Value ComplexExprEmitter::emitBinMul(const BinOpInfo &op) {
return builder.createComplexCreate(op.loc, newReal, newImag);
}
+mlir::Value ComplexExprEmitter::emitBinDiv(const BinOpInfo &op) {
+ assert(!cir::MissingFeatures::fastMathFlags());
+ assert(!cir::MissingFeatures::cgFPOptionsRAII());
+
+ if (mlir::isa<cir::ComplexType>(op.lhs.getType()) &&
+ mlir::isa<cir::ComplexType>(op.rhs.getType())) {
+ cir::ComplexRangeKind rangeKind =
+ getComplexRangeAttr(op.fpFeatures.getComplexRange());
+ return builder.create<cir::ComplexDivOp>(op.loc, op.lhs, op.rhs, rangeKind,
+ fpHasBeenPromoted);
+ }
+
+ cgf.cgm.errorNYI("ComplexExprEmitter::emitBinMu between Complex & Scalar");
+ return {};
+}
+
LValue CIRGenFunction::emitComplexAssignmentLValue(const BinaryOperator *e) {
assert(e->getOpcode() == BO_Assign && "Expected assign op");
@@ -954,6 +1000,14 @@ mlir::Value CIRGenFunction::emitPromotedValue(mlir::Value result,
convertType(promotionType));
}
+mlir::Value CIRGenFunction::emitUnPromotedValue(mlir::Value result,
+ QualType unPromotionType) {
+ assert(!mlir::cast<cir::ComplexType>(result.getType()).isIntegerComplex() &&
+ "integral complex will never be promoted");
+ return builder.createCast(cir::CastKind::float_complex, result,
+ convertType(unPromotionType));
+}
+
LValue CIRGenFunction::emitScalarCompoundAssignWithComplex(
const CompoundAssignOperator *e, mlir::Value &result) {
CompoundFunc op = getComplexOp(e->getOpcode());
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index ddc1edd77010c..6f49a2a25b6b4 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -1302,6 +1302,8 @@ class CIRGenFunction : public CIRGenTypeCache {
LValue emitUnaryOpLValue(const clang::UnaryOperator *e);
+ mlir::Value emitUnPromotedValue(mlir::Value result, QualType unPromotionType);
+
/// Emit a reached-unreachable diagnostic if \p loc is valid and runtime
/// checking is enabled. Otherwise, just emit an unreachable instruction.
/// \p createNewBlock indicates whether to create a new block for the IR
diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
index 66260eb36e002..676b6bfbdb456 100644
--- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
@@ -8,7 +8,6 @@
#include "PassDetail.h"
#include "clang/AST/ASTContext.h"
-#include "clang/AST/CharUnits.h"
#include "clang/CIR/Dialect/Builder/CIRBaseBuilder.h"
#include "clang/CIR/Dialect/IR/CIRDialect.h"
#include "clang/CIR/Dialect/IR/CIROpsEnums.h"
@@ -27,6 +26,7 @@ struct LoweringPreparePass : public LoweringPrepareBase<LoweringPreparePass> {
void runOnOp(mlir::Operation *op);
void lowerCastOp(cir::CastOp op);
+ void lowerComplexDivOp(cir::ComplexDivOp op);
void lowerComplexMulOp(cir::ComplexMulOp op);
void lowerUnaryOp(cir::UnaryOp op);
void lowerArrayDtor(cir::ArrayDtor op);
@@ -181,6 +181,176 @@ static mlir::Value buildComplexBinOpLibCall(
return call.getResult();
}
+static llvm::StringRef
+getComplexDivLibCallName(llvm::APFloat::Semantics semantics) {
+ switch (semantics) {
+ case llvm::APFloat::S_IEEEhalf:
+ return "__divhc3";
+ case llvm::APFloat::S_IEEEsingle:
+ return "__divsc3";
+ case llvm::APFloat::S_IEEEdouble:
+ return "__divdc3";
+ case llvm::APFloat::S_PPCDoubleDouble:
+ return "__divtc3";
+ case llvm::APFloat::S_x87DoubleExtended:
+ return "__divxc3";
+ case llvm::APFloat::S_IEEEquad:
+ return "__divtc3";
+ default:
+ llvm_unreachable("unsupported floating point type");
+ }
+}
+
+static mlir::Value
+buildAlgebraicComplexDiv(CIRBaseBuilderTy &builder, mlir::Location loc,
+ mlir::Value lhsReal, mlir::Value lhsImag,
+ mlir::Value rhsReal, mlir::Value rhsImag) {
+ // (a+bi) / (c+di) = ((ac+bd)/(cc+dd)) + ((bc-ad)/(cc+dd))i
+ mlir::Value &a = lhsReal;
+ mlir::Value &b = lhsImag;
+ mlir::Value &c = rhsReal;
+ mlir::Value &d = rhsImag;
+
+ mlir::Value ac = builder.createBinop(loc, a, cir::BinOpKind::Mul, c); // a*c
+ mlir::Value bd = builder.createBinop(loc, b, cir::BinOpKind::Mul, d); // b*d
+ mlir::Value cc = builder.createBinop(loc, c, cir::BinOpKind::Mul, c); // c*c
+ mlir::Value dd = builder.createBinop(loc, d, cir::BinOpKind::Mul, d); // d*d
+ mlir::Value acbd =
+ builder.createBinop(loc, ac, cir::BinOpKind::Add, bd); // ac+bd
+ mlir::Value ccdd =
+ builder.createBinop(loc, cc, cir::BinOpKind::Add, dd); // cc+dd
+ mlir::Value resultReal =
+ builder.createBinop(loc, acbd, cir::BinOpKind::Div, ccdd);
+
+ mlir::Value bc = builder.createBinop(loc, b, cir::BinOpKind::Mul, c); // b*c
+ mlir::Value ad = builder.createBinop(loc, a, cir::BinOpKind::Mul, d); // a*d
+ mlir::Value bcad =
+ builder.createBinop(loc, bc, cir::BinOpKind::Sub, ad); // bc-ad
+ mlir::Value resultImag =
+ builder.createBinop(loc, bcad, cir::BinOpKind::Div, ccdd);
+ return builder.createComplexCreate(loc, resultReal, resultImag);
+}
+
+static mlir::Value
+buildRangeReductionComplexDiv(CIRBaseBuilderTy &builder, mlir::Location loc,
+ mlir::Value lhsReal, mlir::Value lhsImag,
+ mlir::Value rhsReal, mlir::Value rhsImag) {
+ // Implements Smith's algorithm for complex division.
+ // SMITH, R. L. Algorithm 116: Complex division. Commun. ACM 5, 8 (1962).
+
+ // Let:
+ // - lhs := a+bi
+ // - rhs := c+di
+ // - result := lhs / rhs = e+fi
+ //
+ // The algorithm pseudocode looks like follows:
+ // if fabs(c) >= fabs(d):
+ // r := d / c
+ // tmp := c + r*d
+ // e = (a + b*r) / tmp
+ // f = (b - a*r) / tmp
+ // else:
+ // r := c / d
+ // tmp := d + r*c
+ // e = (a*r + b) / tmp
+ // f = (b*r - a) / tmp
+
+ mlir::Value &a = lhsReal;
+ mlir::Value &b = lhsImag;
+ mlir::Value &c = rhsReal;
+ mlir::Value &d = rhsImag;
+
+ auto trueBranchBuilder = [&](mlir::OpBuilder &, mlir::Location) {
+ mlir::Value r = builder.createBinop(loc, d, cir::BinOpKind::Div,
+ c); // r := d / c
+ mlir::Value rd = builder.createBinop(loc, r, cir::BinOpKind::Mul, d); // r*d
+ mlir::Value tmp = builder.createBinop(loc, c, cir::BinOpKind::Add,
+ rd); // tmp := c + r*d
+
+ mlir::Value br = builder.createBinop(loc, b, cir::BinOpKind::Mul, r); // b*r
+ mlir::Value abr =
+ builder.createBinop(loc, a, cir::BinOpKind::Add, br); // a + b*r
+ mlir::Value e = builder.createBinop(loc, abr, cir::BinOpKind::Div, tmp);
+
+ mlir::Value ar = builder.createBinop(loc, a, cir::BinOpKind::Mul, r); // a*r
+ mlir::Value bar =
+ builder.createBinop(loc, b, cir::BinOpKind::Sub, ar); // b - a*r
+ mlir::Value f = builder.createBinop(loc, bar, cir::BinOpKind::Div, tmp);
+
+ mlir::Value result = builder.createComplexCreate(loc, e, f);
+ builder.createYield(loc, result);
+ };
+
+ auto falseBranchBuilder = [&](mlir::OpBuilder &, mlir::Location) {
+ mlir::Value r = builder.createBinop(loc, c, cir::BinOpKind::Div,
+ d); // r := c / d
+ mlir::Value rc = builder.createBinop(loc, r, cir::BinOpKind::Mul, c); // r*c
+ mlir::Value tmp = builder.createBinop(loc, d, cir::BinOpKind::Add,
+ rc); // tmp := d + r*c
+
+ mlir::Value ar = builder.createBinop(loc, a, cir::BinOpKind::Mul, r); // a*r
+ mlir::Value arb =
+ builder.createBinop(loc, ar, cir::BinOpKind::Add, b); // a*r + b
+ mlir::Value e = builder.createBinop(loc, arb, cir::BinOpKind::Div, tmp);
+
+ mlir::Value br = builder.createBinop(loc, b, cir::BinOpKind::Mul, r); // b*r
+ mlir::Value bra =
+ builder.createBinop(loc, br, cir::BinOpKind::Sub, a); // b*r - a
+ mlir::Value f = builder.createBinop(loc, bra, cir::BinOpKind::Div, tmp);
+
+ mlir::Value result = builder.createComplexCreate(loc, e, f);
+ builder.createYield(loc, result);
+ };
+
+ auto cFabs = builder.create<cir::FAbsOp>(loc, c);
+ auto dFabs = builder.create<cir::FAbsOp>(loc, d);
+ cir::CmpOp cmpResult =
+ builder.createCompare(loc, cir::CmpOpKind::ge, cFabs, dFabs);
+ auto ternary = builder.create<cir::TernaryOp>(
+ loc, cmpResult, trueBranchBuilder, falseBranchBuilder);
+
+ return ternary.getResult();
+}
+
+static mlir::Value lowerComplexDiv(LoweringPreparePass &pass,
+ CIRBaseBuilderTy &builder,
+ mlir::Location loc, cir::ComplexDivOp op,
+ mlir::Value lhsReal, mlir::Value lhsImag,
+ mlir::Value rhsReal, mlir::Value rhsImag) {
+ cir::ComplexType complexTy = op.getType();
+ if (mlir::isa<cir::FPTypeInterface>(complexTy.getElementType())) {
+ cir::ComplexRangeKind range = op.getRange();
+ if (range == cir::ComplexRangeKind::Improved ||
+ (range == cir::ComplexRangeKind::Promoted && !op.getPromoted()))
+ return buildRangeReductionComplexDiv(builder, loc, lhsReal, lhsImag,
+ rhsReal, rhsImag);
+ if (range == cir::ComplexRangeKind::Full)
+ return buildComplexBinOpLibCall(pass, builder, &getComplexDivLibCallName,
+ loc, complexTy, lhsReal, lhsImag, rhsReal,
+ rhsImag);
+ }
+
+ return buildAlgebraicComplexDiv(builder, loc, lhsReal, lhsImag, rhsReal,
+ rhsImag);
+}
+
+void LoweringPreparePass::lowerComplexDivOp(cir::ComplexDivOp op) {
+ cir::CIRBaseBuilderTy builder(getContext());
+ builder.setInsertionPointAfter(op);
+ mlir::Location loc = op.getLoc();
+ mlir::TypedValue<cir::ComplexType> lhs = op.getLhs();
+ mlir::TypedValue<cir::ComplexType> rhs = op.getRhs();
+ mlir::Value lhsReal = builder.createComplexReal(loc, lhs);
+ mlir::Value lhsImag = builder.createComplexImag(loc, lhs);
+ mlir::Value rhsReal = builder.createComplexReal(loc, rhs);
+ mlir::Value rhsImag = builder.createComplexImag(loc, rhs);
+
+ mlir::Value loweredResult = lowerComplexDiv(*this, builder, loc, op, lhsReal,
+ lhsImag, rhsReal, rhsImag);
+ op.replaceAllUsesWith(loweredResult);
+ op.erase();
+}
+
static llvm::StringRef
getComplexMulLibCallName(llvm::APFloat::Semantics semantics) {
switch (semantics) {
@@ -412,6 +582,8 @@ void LoweringPreparePass::runOnOp(mlir::Operation *op) {
lowerArrayDtor(arrayDtor);
else if (auto cast = mlir::dyn_cast<cir::CastOp>(op))
lowerCastOp(cast);
+ else if (auto complexDiv = mlir::dyn_cast<cir::ComplexDivOp>(op))
+ lowerComplexDivOp(complexDiv);
else if (auto complexMul = mlir::dyn_cast<cir::ComplexMulOp>(op))
lowerComplexMulOp(complexMul);
else if (auto unary = mlir::dyn_cast<cir::UnaryOp>(op))
@@ -427,7 +599,7 @@ void LoweringPreparePass::runOnOperation() {
op->walk([&](mlir::Operation *op) {
if (mlir::isa<cir::ArrayCtor, cir::ArrayDtor, cir::CastOp,
- cir::ComplexMulOp, cir::UnaryOp>(op))
+ cir::ComplexMulOp, cir::ComplexDivOp, cir::UnaryOp>(op))
opsToTransform.push_back(op);
});
diff --git a/clang/test/CIR/CodeGen/complex-mul-div.cpp b/clang/test/CIR/CodeGen/complex-mul-div.cpp
index 633080577092c..aa44b72e6aaa3 100644
--- a/clang/test/CIR/CodeGen/complex-mul-div.cpp
+++ b/clang/test/CIR/CodeGen/complex-mul-div.cpp
@@ -3,27 +3,27 @@
// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -complex-range=basic -Wno-unused-value -fclangir -emit-cir %s -o %t.cir
// RUN: FileCheck --input-file=%t.cir %s --check-prefixes=CIR-AFTER-INT,CIR-AFTER-MUL-COMBINED,CIR-COMBINED
// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -complex-range=basic -Wno-unused-value -fclangir -emit-llvm %s -o %t-cir.ll
-// RUN: FileCheck --input-file=%t-cir.ll %s --check-prefixes=LLVM-INT,LLVM-MUL-COMBINED,LLVM-COMBINED
+// RUN: FileCheck --input-file=%t-cir.ll %s --check-prefixes=LLVM-INT,LLVM-MUL-COMBINED,LLVM-COMBINED,LLVM-BASIC
// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -complex-range=basic -Wno-unused-value -emit-llvm %s -o %t.ll
-// RUN: FileCheck --input-file=%t.ll %s --check-prefixes=OGCG-INT,OGCG-MUL-COMBINED,OGCG-COMBINED
+// RUN: FileCheck --input-file=%t.ll %s --check-prefixes=OGCG-INT,OGCG-MUL-COMBINED,OGCG-COMBINED,OGCG-BASIC
// complex-range improved
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -complex-range=improved -Wno-unused-value -fclangir -emit-cir -mmlir --mlir-print-ir-before=cir-canonicalize -o %t.cir %s 2>&1 | FileCheck --check-prefix=CIR-BEFORE-IMPROVED %s
// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -complex-range=improved -Wno-unused-value -fclangir -emit-cir %s -o %t.cir
// RUN: FileCheck --input-file=%t.cir %s --check-prefixes=CIR-AFTER-INT,CIR-AFTER-MUL-COMBINED,CIR-COMBINED
// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -complex-range=improved -Wno-unused-value -fclangir -emit-llvm %s -o %t-cir.ll
-// RUN: FileCheck --input-file=%t-cir.ll %s --check-prefixes=LLVM-INT,LLVM-MUL-COMBINED,LLVM-COMBINED
+// RUN: FileCheck --input-file=%t-cir.ll %s --check-prefixes=LLVM-INT,LLVM-MUL-COMBINED,LLVM-COMBINED,LLVM-IMPROVED
// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -complex-range=improved -Wno-unused-value -emit-llvm %s -o %t.ll
-// RUN: FileCheck --input-file=%t.ll %s --check-prefixes=OGCG-INT,OGCG-MUL-COMBINED,OGCG-COMBINED
+// RUN: FileCheck --input-file=%t.ll %s --check-prefixes=OGCG-INT,OGCG-MUL-COMBINED,OGCG-COMBINED,OGCG-IMPROVED
// complex-range promoted
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -complex-range=promoted -Wno-unused-value -fclangir -emit-cir -mmlir --mlir-print-ir-before=cir-canonicalize -o %t.cir %s 2>&1 | FileCheck --check-prefix=CIR-BEFORE-PROMOTED %s
// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -complex-range=promoted -Wno-unused-value -fclangir -emit-cir %s -o %t.cir
// RUN: FileCheck --input-file=%t.cir %s --check-prefixes=CIR-AFTER-INT,CIR-AFTER-MUL-COMBINED,CIR-COMBINED
// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -complex-range=promoted -Wno-unused-value -fclangir -emit-llvm %s -o %t-cir.ll
-// RUN: FileCheck --input-file=%t-cir.ll %s --check-prefixes=LLVM-INT,LLVM-MUL-COMBINED,LLVM-COMBINED
+// RUN: FileCheck --input-file=%t-cir.ll %s --check-prefixes=LLVM-INT,LLVM-MUL-COMBINED,LLVM-COMBINED,LLVM-P...
[truncated]
|
let summary = "Complex division"; | ||
let description = [{ | ||
The `cir.complex.div` operation takes two complex numbers and returns | ||
their division. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
their division. | |
their quotient. |
The `cir.complex.div` operation takes two complex numbers and returns | ||
their division. | ||
|
||
Range is used to select the implementation used when the operation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Range is used to select the implementation used when the operation | |
The `range` attribute is used to select the algorithm used when the operation |
|
||
Range is used to select the implementation used when the operation | ||
is lowered to the LLVM dialect. For division, 'improved' and | ||
'promoted' are all handled equivalently, producing the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is wrong. For 'promoted' the values are promoted to a higher precision type, if possible, and the calculation is performed using the algebraic formula. We only fall back on Smith's algorithm when the target does not support a higher precision type. Also, this only applies to floating-point types. This description should mention that for integer-based complex values the algebraic formula is always used and 'range' is ignored.
CIR_ComplexType:$lhs, | ||
CIR_ComplexType:$rhs, | ||
CIR_ComplexRangeKind:$range, | ||
UnitAttr:$promoted |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this here in addition to $range
?
Smith's algorithms for Complex division. If 'full' is used, | ||
a runtime-library function is called if one of the intermediate | ||
calculations produced a NaN value, and for 'basic' algebraic formula with | ||
no special handling for NaN value will be used. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should also mention that no special handling for NaN values is used with 'improved' or 'promoted'.
// Check that the promoted type can handle the intermediate values without | ||
// overflowing. This can be interpreted as: | ||
// (SmallerType.LargestFiniteVal * SmallerType.LargestFiniteVal) * 2 <= | ||
// LargerType.LargestFiniteVal. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// LargerType.LargestFiniteVal. | |
// LargerType.LargestFiniteVal. |
|
||
QualType getPromotionType(QualType ty, bool isDivOpCode = false) { | ||
if (auto *complexTy = ty->getAs<ComplexType>()) { | ||
QualType elementTy = complexTy->getElementType(); | ||
if (isDivOpCode && elementTy->isFloatingType() && | ||
cgf.getLangOpts().getComplexRange() == | ||
LangOptions::ComplexRangeKind::CX_Promoted) { | ||
cgf.cgm.errorNYI("HigherPrecisionTypeForComplexArithmetic"); | ||
return QualType(); | ||
return higherPrecisionTypeForComplexArithmetic(elementTy, isDivOpCode); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be deferred until lowering. There is no reason to promote the type in the initial CIR representation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will defer that to lower preparation and make the condition depend on mlir::Type, not QualType (Not available in LoweringPrepare)
// OGCG-IMPROVED: store float %[[RESULT_REAL]], ptr %[[C_REAL_PTR]], align 4 | ||
// OGCG-IMPROVED: store float %[[RESULT_IMAG]], ptr %[[C_IMAG_PTR]], align 4 | ||
|
||
// CIR-BEFORE-PROMOTED: %{{.*}} = cir.complex.div {{.*}}, {{.*}} range(promoted) : !cir.complex<!cir.double> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is wrong. The higher precision type should only be used for the intermediate calculation. The operation should appear with !cir.complex<!cir.float>
inputs and output at this level.
return builder.create<cir::ComplexDivOp>(op.loc, op.lhs, op.rhs, rangeKind); | ||
} | ||
|
||
cgf.cgm.errorNYI("ComplexExprEmitter::emitBinMu between Complex & Scalar"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cgf.cgm.errorNYI("ComplexExprEmitter::emitBinMu between Complex & Scalar"); | |
cgf.cgm.errorNYI("ComplexExprEmitter::emitBinDiv between Complex & Scalar"); |
mlir::isa<cir::ComplexType>(op.rhs.getType())) { | ||
cir::ComplexRangeKind rangeKind = | ||
getComplexRangeAttr(op.fpFeatures.getComplexRange()); | ||
return builder.create<cir::ComplexDivOp>(op.loc, op.lhs, op.rhs, rangeKind); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return builder.create<cir::ComplexDivOp>(op.loc, op.lhs, op.rhs, rangeKind); | |
return cir::ComplexDivOp::create(builder, op.loc, op.lhs, op.rhs, rangeKind); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good. I just have some suggestions for clarifying the description.
|
||
The `range` attribute is used to select the algorithm used when | ||
the operation is lowered to the LLVM dialect. For division, 'improved' | ||
producing the Smith's algorithms for Complex division with no special |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
producing the Smith's algorithms for Complex division with no special | |
produces Smith's algorithms for Complex division with no additional |
Smith's algorithm is a way of avoiding most intermediate NaN values,so 'additional' is better here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you so much for the suggestions and improvements. I applied them and aligned MulOp
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a statment mentioning that the range
attribute is ignored for complex integer types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can add one, but I think we already mentioned that it's for FP. Does it still need that note?
For complex types with floating-point components, the
range
attribute specifies the algorithm to be used when
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a note at the end about that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's helpful to be explicit about what happens. The FP note says what range
means for FP types, but it doesn't explicitly say what happens for other types.
using the algebraic formula. We only fall back on Smith's algorithm when | ||
the target does not support a higher precision type. Also, this only | ||
applies to floating-point types with no special handling for NaN values. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
using the algebraic formula. We only fall back on Smith's algorithm when | |
the target does not support a higher precision type. Also, this only | |
applies to floating-point types with no special handling for NaN values. | |
using the algebraic formula, with no additional handling for NaN values. | |
We fall back on Smith's algorithm when the target does not support a | |
higher precision type. |
The comment about floating-point types applies to this entire paragraph. For integer types, the range
attribute is ignored and we always lower to the algebraic formula. It's probably best to say that in a separate paragraph.
The `cir.complex.div` operation takes two complex numbers and returns | ||
their quotient. | ||
|
||
The `range` attribute is used to select the algorithm used when |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The `range` attribute is used to select the algorithm used when | |
For complex types with floating-point components, the `range` attribute specifies the algorithm to be used when |
when the target does not support a higher precision type. | ||
If 'full' is used, a runtime-library function is called if one of the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The formatting is a bit off here.
This change adds support for the division operation between two complex types
Issue: #141365