Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -2966,7 +2966,7 @@ def CIR_ComplexSubOp : CIR_Op<"complex.sub", [
}

//===----------------------------------------------------------------------===//
// ComplexMulOp
// ComplexMulOp & ComplexDivOp
//===----------------------------------------------------------------------===//

def CIR_ComplexRangeKind : CIR_I32EnumAttr<
Expand Down Expand Up @@ -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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
their division.
their quotient.


Range is used to select the implementation used when the operation
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Range is used to select the implementation used when the operation
The `range` attribute is used to select the algorithm used when the operation

is lowered to the LLVM dialect. For division, 'improved' and
'promoted' are all handled equivalently, producing the
Copy link
Contributor

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.

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.
Copy link
Contributor

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'.


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
Copy link
Contributor

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?

);

let results = (outs CIR_ComplexType:$result);

let assemblyFormat = [{
$lhs `,` $rhs `range` `(` $range `)` `:` qualified(type($result)) attr-dict
}];
}

//===----------------------------------------------------------------------===//
// Bit Manipulation Operations
//===----------------------------------------------------------------------===//
Expand Down
60 changes: 57 additions & 3 deletions clang/lib/CIR/CodeGen/CIRGenExprComplex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ namespace {
class ComplexExprEmitter : public StmtVisitor<ComplexExprEmitter, mlir::Value> {
CIRGenFunction &cgf;
CIRGenBuilderTy &builder;
bool fpHasBeenPromoted = false;

public:
explicit ComplexExprEmitter(CIRGenFunction &cgf)
Expand Down Expand Up @@ -128,15 +129,43 @@ 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// LargerType.LargestFiniteVal.
// 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>()) {
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);
Copy link
Contributor

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.

Copy link
Member Author

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)

}

if (elementTy.UseExcessPrecision(cgf.getContext()))
Expand All @@ -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.
Expand Down Expand Up @@ -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");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
cgf.cgm.errorNYI("ComplexExprEmitter::emitBinMu between Complex & Scalar");
cgf.cgm.errorNYI("ComplexExprEmitter::emitBinDiv between Complex & Scalar");

return {};
}

LValue CIRGenFunction::emitComplexAssignmentLValue(const BinaryOperator *e) {
assert(e->getOpcode() == BO_Assign && "Expected assign op");

Expand Down Expand Up @@ -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());
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
176 changes: 174 additions & 2 deletions clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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))
Expand All @@ -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);
});

Expand Down
Loading