Skip to content

[flang] Add -fcomplex-arithmetic= option and select complex division algorithm #146641

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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 6 additions & 0 deletions clang/include/clang/Driver/CommonArgs.h
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,12 @@ StringRef parseMPreferVectorWidthOption(clang::DiagnosticsEngine &Diags,
StringRef parseMRecipOption(clang::DiagnosticsEngine &Diags,
const llvm::opt::ArgList &Args);

// Convert ComplexRangeKind to a string that can be passed as a frontend option.
std::string ComplexRangeKindToStr(LangOptions::ComplexRangeKind Range);

// Render a frontend option corresponding to ComplexRangeKind.
std::string RenderComplexRangeOption(LangOptions::ComplexRangeKind Range);

} // end namespace tools
} // end namespace driver
} // end namespace clang
Expand Down
7 changes: 4 additions & 3 deletions clang/include/clang/Driver/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -1023,12 +1023,13 @@ defm offload_uniform_block : BoolFOption<"offload-uniform-block",
BothFlags<[], [ClangOption], " that kernels are launched with uniform block sizes (default true for CUDA/HIP and false otherwise)">>;

def fcomplex_arithmetic_EQ : Joined<["-"], "fcomplex-arithmetic=">, Group<f_Group>,
Visibility<[ClangOption, CC1Option]>,
Visibility<[ClangOption, CC1Option, FlangOption, FC1Option]>,
Values<"full,improved,promoted,basic">, NormalizedValuesScope<"LangOptions">,
NormalizedValues<["CX_Full", "CX_Improved", "CX_Promoted", "CX_Basic"]>;
NormalizedValues<["CX_Full", "CX_Improved", "CX_Promoted", "CX_Basic"]>,
HelpText<"Controls the calculation methods of complex number multiplication and division.">;

def complex_range_EQ : Joined<["-"], "complex-range=">, Group<f_Group>,
Visibility<[CC1Option]>,
Visibility<[CC1Option, FC1Option]>,
Values<"full,improved,promoted,basic">, NormalizedValuesScope<"LangOptions">,
NormalizedValues<["CX_Full", "CX_Improved", "CX_Promoted", "CX_Basic"]>,
MarshallingInfoEnum<LangOpts<"ComplexRange">, "CX_Full">;
Expand Down
27 changes: 0 additions & 27 deletions clang/lib/Driver/ToolChains/Clang.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2740,25 +2740,6 @@ static void CollectArgsForIntegratedAssembler(Compilation &C,
}
}

static std::string ComplexRangeKindToStr(LangOptions::ComplexRangeKind Range) {
switch (Range) {
case LangOptions::ComplexRangeKind::CX_Full:
return "full";
break;
case LangOptions::ComplexRangeKind::CX_Basic:
return "basic";
break;
case LangOptions::ComplexRangeKind::CX_Improved:
return "improved";
break;
case LangOptions::ComplexRangeKind::CX_Promoted:
return "promoted";
break;
default:
return "";
}
}

static std::string ComplexArithmeticStr(LangOptions::ComplexRangeKind Range) {
return (Range == LangOptions::ComplexRangeKind::CX_None)
? ""
Expand All @@ -2772,14 +2753,6 @@ static void EmitComplexRangeDiag(const Driver &D, std::string str1,
}
}

static std::string
RenderComplexRangeOption(LangOptions::ComplexRangeKind Range) {
std::string ComplexRangeStr = ComplexRangeKindToStr(Range);
if (!ComplexRangeStr.empty())
return "-complex-range=" + ComplexRangeStr;
return ComplexRangeStr;
}

static void RenderFloatingPointOptions(const ToolChain &TC, const Driver &D,
bool OFastEnabled, const ArgList &Args,
ArgStringList &CmdArgs,
Expand Down
27 changes: 27 additions & 0 deletions clang/lib/Driver/ToolChains/CommonArgs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3409,3 +3409,30 @@ StringRef tools::parseMRecipOption(clang::DiagnosticsEngine &Diags,

return Out;
}

std::string tools::ComplexRangeKindToStr(LangOptions::ComplexRangeKind Range) {
switch (Range) {
case LangOptions::ComplexRangeKind::CX_Full:
return "full";
break;
case LangOptions::ComplexRangeKind::CX_Basic:
return "basic";
break;
case LangOptions::ComplexRangeKind::CX_Improved:
return "improved";
break;
case LangOptions::ComplexRangeKind::CX_Promoted:
return "promoted";
break;
default:
return "";
}
}

std::string
tools::RenderComplexRangeOption(LangOptionsBase::ComplexRangeKind Range) {
std::string ComplexRangeStr = ComplexRangeKindToStr(Range);
if (!ComplexRangeStr.empty())
return "-complex-range=" + ComplexRangeStr;
return ComplexRangeStr;
}
23 changes: 23 additions & 0 deletions clang/lib/Driver/ToolChains/Flang.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,8 @@ static void addFloatingPointOptions(const Driver &D, const ArgList &Args,
bool AssociativeMath = false;
bool ReciprocalMath = false;

LangOptions::ComplexRangeKind Range = LangOptions::ComplexRangeKind::CX_None;

if (const Arg *A = Args.getLastArg(options::OPT_ffp_contract)) {
const StringRef Val = A->getValue();
if (Val == "fast" || Val == "off") {
Expand All @@ -629,6 +631,20 @@ static void addFloatingPointOptions(const Driver &D, const ArgList &Args,
default:
continue;

case options::OPT_fcomplex_arithmetic_EQ: {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this can be moved to CommonArgs.cpp as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Although Clang implements CX_Promoted, the corresponding conversion is not implemented in Flang. Therefore I want the driver to emit an error. Since this behavior differs slightly from Clang, moving this to CommonArgs.cpp might be difficult.

StringRef Val = A->getValue();
if (Val == "full")
Range = LangOptions::ComplexRangeKind::CX_Full;
else if (Val == "improved")
Range = LangOptions::ComplexRangeKind::CX_Improved;
else if (Val == "basic")
Range = LangOptions::ComplexRangeKind::CX_Basic;
else {
D.Diag(diag::err_drv_unsupported_option_argument)
<< A->getSpelling() << Val;
}
break;
}
case options::OPT_fhonor_infinities:
HonorINFs = true;
break;
Expand Down Expand Up @@ -699,6 +715,13 @@ static void addFloatingPointOptions(const Driver &D, const ArgList &Args,
if (!Recip.empty())
CmdArgs.push_back(Args.MakeArgString("-mrecip=" + Recip));

if (Range != LangOptions::ComplexRangeKind::CX_None) {
std::string ComplexRangeStr = RenderComplexRangeOption(Range);
CmdArgs.push_back(Args.MakeArgString(ComplexRangeStr));
CmdArgs.push_back(Args.MakeArgString("-fcomplex-arithmetic=" +
ComplexRangeKindToStr(Range)));
}

if (!HonorINFs && !HonorNaNs && AssociativeMath && ReciprocalMath &&
ApproxFunc && !SignedZeros &&
(FPContract == "fast" || FPContract.empty())) {
Expand Down
1 change: 1 addition & 0 deletions flang/include/flang/Frontend/CodeGenOptions.def
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ ENUM_CODEGENOPT(RelocationModel, llvm::Reloc::Model, 3, llvm::Reloc::PIC_) ///<
ENUM_CODEGENOPT(DebugInfo, llvm::codegenoptions::DebugInfoKind, 4, llvm::codegenoptions::NoDebugInfo) ///< Level of debug info to generate
ENUM_CODEGENOPT(VecLib, llvm::driver::VectorLibrary, 4, llvm::driver::VectorLibrary::NoLibrary) ///< Vector functions library to use
ENUM_CODEGENOPT(FramePointer, llvm::FramePointerKind, 2, llvm::FramePointerKind::None) ///< Enable the usage of frame pointers
ENUM_CODEGENOPT(ComplexRange, ComplexRangeKind, 3, ComplexRangeKind::CX_Full) ///< Method for calculating complex number division

ENUM_CODEGENOPT(DoConcurrentMapping, DoConcurrentMappingKind, 2, DoConcurrentMappingKind::DCMK_None) ///< Map `do concurrent` to OpenMP

Expand Down
25 changes: 25 additions & 0 deletions flang/include/flang/Frontend/CodeGenOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,31 @@ class CodeGenOptions : public CodeGenOptionsBase {
return getProfileUse() == llvm::driver::ProfileCSIRInstr;
}

/// Controls the various implementations for complex division.
Copy link
Contributor

Choose a reason for hiding this comment

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

If this is exactly the same as the enum in clang/Basic/LangOptions, it could be moved to llvm/Frontend/Driver/CodeGenOptions.h and shared between clang and flang. There is precedence for this, most recently, here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for providing the example! I understand that your example is about moving Clang's CodeGenOptions to llvm/Frontend/Driver/CodegenOptions.h. Since Flang already defines ComplexRangeKind as CodeGenOptions, moving it to llvm/Frontend/Driver/CodegenOptions.h might not require significant modifications. However, in Clang, it's defined in LangOptions, so I'm unsure how to move it and how much modification would be necessary.

enum ComplexRangeKind {
/// Implementation of complex division using a call to runtime library
/// functions. Overflow and non-finite values are handled by the library
/// implementation. This is the default value.
CX_Full,

/// Implementation of complex division offering an improved handling
/// for overflow in intermediate calculations. Overflow and non-finite
/// values are handled by MLIR's implementation of "complex.div", but this
/// may change in the future.
CX_Improved,

/// Implementation of complex division using algebraic formulas at source
/// precision. No special handling to avoid overflow. NaN and infinite
/// values are not handled.
CX_Basic,

/// No range rule is enabled.
CX_None

/// TODO: Implemention of other values as needed. In Clang, "CX_Promoted"
/// is implemented. (See clang/Basic/LangOptions.h)
};

// Define accessors/mutators for code generation options of enumeration type.
#define CODEGENOPT(Name, Bits, Default)
#define ENUM_CODEGENOPT(Name, Type, Bits, Default) \
Expand Down
4 changes: 4 additions & 0 deletions flang/include/flang/Lower/LoweringOptions.def
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,9 @@ ENUM_LOWERINGOPT(CUDARuntimeCheck, unsigned, 1, 0)
/// derived types defined in other compilation units.
ENUM_LOWERINGOPT(SkipExternalRttiDefinition, unsigned, 1, 0)

/// If true, convert complex number division to runtime on the frontend.
/// If false, lower to the complex dialect of MLIR.
/// On by default.
ENUM_LOWERINGOPT(ComplexDivisionToRuntime, unsigned, 1, 1)
#undef LOWERINGOPT
#undef ENUM_LOWERINGOPT
15 changes: 15 additions & 0 deletions flang/include/flang/Optimizer/Builder/FIRBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,17 @@ class FirOpBuilder : public mlir::OpBuilder, public mlir::OpBuilder::Listener {
return integerOverflowFlags;
}

/// Set ComplexDivisionToRuntimeFlag value. If set to true, complex number
/// division is lowered to a runtime function by this builder.
void setComplexDivisionToRuntimeFlag(bool flag) {
complexDivisionToRuntimeFlag = flag;
}

/// Get current ComplexDivisionToRuntimeFlag value.
bool getComplexDivisionToRuntimeFlag() const {
return complexDivisionToRuntimeFlag;
}

/// Dump the current function. (debug)
LLVM_DUMP_METHOD void dumpFunc();

Expand Down Expand Up @@ -673,6 +684,10 @@ class FirOpBuilder : public mlir::OpBuilder, public mlir::OpBuilder::Listener {
/// mlir::arith::IntegerOverflowFlagsAttr.
mlir::arith::IntegerOverflowFlags integerOverflowFlags{};

/// Flag to control whether complex number division is lowered to a runtime
/// function or to the MLIR complex dialect.
bool complexDivisionToRuntimeFlag = true;

/// fir::GlobalOp and func::FuncOp symbol table to speed-up
/// lookups.
mlir::SymbolTable *symbolTable = nullptr;
Expand Down
6 changes: 6 additions & 0 deletions flang/include/flang/Optimizer/CodeGen/CodeGen.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#ifndef FORTRAN_OPTIMIZER_CODEGEN_CODEGEN_H
#define FORTRAN_OPTIMIZER_CODEGEN_CODEGEN_H

#include "flang/Frontend/CodeGenOptions.h"
Copy link
Contributor

Choose a reason for hiding this comment

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

Not requesting to do that here, but I feel the CodeGenOptions should be defined in Codegen and used/set in Frontend rather than having Codegen depends on Frontend things I think.
This can be refactored independently and is not a huge deal for a header use without adding a library linking dependency.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think the CodeGenOptions.h header is included because of the ComplexRangeKind enum. If that is moved to llvm/Frontend/Driver/CodeGenOptions.h, we don't have this issue. It may be worth doing it in this PR, but I am ok with moving it in a separate PR as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for the reviews!

Not requesting to do that here, but I feel the CodeGenOptions should be defined in Codegen and used/set in Frontend rather than having Codegen depends on Frontend things I think.

Does this mean that ComplexRangeKind should be defined in CodeGen.h instead of CodeGenOptions.h, and that CodeGenOptions should reference it?

I think the CodeGenOptions.h header is included because of the ComplexRangeKind enum. If that is moved to llvm/Frontend/Driver/CodeGenOptions.h, we don't have this issue. It may be worth doing it in this PR, but I am ok with moving it in a separate PR as well.

Yes, I'm including CodeGenOptions.h to use the ComplexRangeKind enum. I'll also try moving it to llvm/Frontend/Driver/CodeGenOptions.h, but I think that would be a separate PR.

#include "mlir/IR/BuiltinOps.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Pass/PassRegistry.h"
Expand Down Expand Up @@ -58,6 +59,11 @@ struct FIRToLLVMPassOptions {
// the name of the global variable corresponding to a derived
// type's descriptor.
bool typeDescriptorsRenamedForAssembly = false;

// Specify the calculation method for complex number division used by the
// Conversion pass of the MLIR complex dialect.
Fortran::frontend::CodeGenOptions::ComplexRangeKind ComplexRange =
Fortran::frontend::CodeGenOptions::ComplexRangeKind::CX_Full;
};

/// Convert FIR to the LLVM IR dialect with default options.
Expand Down
3 changes: 3 additions & 0 deletions flang/include/flang/Tools/CrossToolHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,9 @@ struct MLIRToLLVMPassPipelineConfig : public FlangEPCallBacks {
std::string InstrumentFunctionExit =
""; ///< Name of the instrument-function that is called on each
///< function-exit
Fortran::frontend::CodeGenOptions::ComplexRangeKind ComplexRange =
Fortran::frontend::CodeGenOptions::ComplexRangeKind::
CX_Full; ///< Method for calculating complex number division
};

struct OffloadModuleOpts {
Expand Down
21 changes: 21 additions & 0 deletions flang/lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,21 @@ static void parseCodeGenArgs(Fortran::frontend::CodeGenOptions &opts,
}

parseDoConcurrentMapping(opts, args, diags);

if (const llvm::opt::Arg *arg =
args.getLastArg(clang::driver::options::OPT_complex_range_EQ)) {
llvm::StringRef argValue = llvm::StringRef(arg->getValue());
if (argValue == "full") {
opts.setComplexRange(CodeGenOptions::ComplexRangeKind::CX_Full);
} else if (argValue == "improved") {
opts.setComplexRange(CodeGenOptions::ComplexRangeKind::CX_Improved);
} else if (argValue == "basic") {
opts.setComplexRange(CodeGenOptions::ComplexRangeKind::CX_Basic);
} else {
diags.Report(clang::diag::err_drv_invalid_value)
<< arg->getAsString(args) << arg->getValue();
}
}
}

/// Parses all target input arguments and populates the target
Expand Down Expand Up @@ -1811,4 +1826,10 @@ void CompilerInvocation::setLoweringOptions() {
.setNoSignedZeros(langOptions.NoSignedZeros)
.setAssociativeMath(langOptions.AssociativeMath)
.setReciprocalMath(langOptions.ReciprocalMath);

if (codegenOpts.getComplexRange() ==
CodeGenOptions::ComplexRangeKind::CX_Improved ||
codegenOpts.getComplexRange() ==
CodeGenOptions::ComplexRangeKind::CX_Basic)
loweringOpts.setComplexDivisionToRuntime(false);
}
2 changes: 2 additions & 0 deletions flang/lib/Frontend/FrontendActions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,8 @@ void CodeGenAction::generateLLVMIR() {
if (ci.getInvocation().getLoweringOpts().getIntegerWrapAround())
config.NSWOnLoopVarInc = false;

config.ComplexRange = opts.getComplexRange();

// Create the pass pipeline
fir::createMLIRToLLVMPassPipeline(pm, config, getCurrentFile());
(void)mlir::applyPassManagerCLOptions(pm);
Expand Down
2 changes: 2 additions & 0 deletions flang/lib/Lower/Bridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5746,6 +5746,8 @@ class FirConverter : public Fortran::lower::AbstractConverter {
builder =
new fir::FirOpBuilder(func, bridge.getKindMap(), &mlirSymbolTable);
assert(builder && "FirOpBuilder did not instantiate");
builder->setComplexDivisionToRuntimeFlag(
bridge.getLoweringOptions().getComplexDivisionToRuntime());
builder->setFastMathFlags(bridge.getLoweringOptions().getMathOptions());
builder->setInsertionPointToStart(&func.front());
if (funit.parent.isA<Fortran::lower::pft::FunctionLikeUnit>()) {
Expand Down
13 changes: 11 additions & 2 deletions flang/lib/Lower/ConvertExprToHLFIR.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1066,8 +1066,17 @@ struct BinaryOp<Fortran::evaluate::Divide<
mlir::Type ty = Fortran::lower::getFIRType(
builder.getContext(), Fortran::common::TypeCategory::Complex, KIND,
/*params=*/{});
return hlfir::EntityWithAttributes{
fir::genDivC(builder, loc, ty, lhs, rhs)};

// TODO: Ideally, complex number division operations should always be
// lowered to MLIR. However, converting them to the runtime via MLIR causes
// ABI issues.
if (builder.getComplexDivisionToRuntimeFlag()) {
return hlfir::EntityWithAttributes{
fir::genDivC(builder, loc, ty, lhs, rhs)};
} else {
return hlfir::EntityWithAttributes{
builder.create<mlir::complex::DivOp>(loc, lhs, rhs)};
}
}
};

Expand Down
15 changes: 14 additions & 1 deletion flang/lib/Optimizer/CodeGen/CodeGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4119,7 +4119,20 @@ class FIRToLLVMLowering
mathToFuncsOptions.minWidthOfFPowIExponent = 33;
mathConvertionPM.addPass(
mlir::createConvertMathToFuncs(mathToFuncsOptions));
mathConvertionPM.addPass(mlir::createConvertComplexToStandardPass());

mlir::ConvertComplexToStandardPassOptions complexToStandardOptions{};
if (options.ComplexRange ==
Fortran::frontend::CodeGenOptions::ComplexRangeKind::CX_Basic) {
complexToStandardOptions.complexRange =
mlir::complex::ComplexRangeFlags::basic;
} else if (options.ComplexRange == Fortran::frontend::CodeGenOptions::
ComplexRangeKind::CX_Improved) {
complexToStandardOptions.complexRange =
mlir::complex::ComplexRangeFlags::improved;
}
mathConvertionPM.addPass(
mlir::createConvertComplexToStandardPass(complexToStandardOptions));

// Convert Math dialect operations into LLVM dialect operations.
// There is no way to prefer MathToLLVM patterns over MathToLibm
// patterns (applied below), so we have to run MathToLLVM conversion here.
Expand Down
1 change: 1 addition & 0 deletions flang/lib/Optimizer/Passes/Pipelines.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ void addFIRToLLVMPass(mlir::PassManager &pm,
options.forceUnifiedTBAATree = useOldAliasTags;
options.typeDescriptorsRenamedForAssembly =
!disableCompilerGeneratedNamesConversion;
options.ComplexRange = config.ComplexRange;
addPassConditionally(pm, disableFirToLlvmIr,
[&]() { return fir::createFIRToLLVMPass(options); });
// The dialect conversion framework may leave dead unrealized_conversion_cast
Expand Down
23 changes: 23 additions & 0 deletions flang/test/Driver/complex-range.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
! Test range options for complex multiplication and division.

! RUN: %flang -### -c %s 2>&1 \
! RUN: | FileCheck %s --check-prefix=RANGE

! RUN: %flang -### -fcomplex-arithmetic=full -c %s 2>&1 \
! RUN: | FileCheck %s --check-prefix=FULL

! RUN: %flang -### -fcomplex-arithmetic=improved -c %s 2>&1 \
! RUN: | FileCheck %s --check-prefix=IMPRVD

! RUN: %flang -### -fcomplex-arithmetic=basic -c %s 2>&1 \
! RUN: | FileCheck %s --check-prefix=BASIC

! RUN: not %flang -### -fcomplex-arithmetic=foo -c %s 2>&1 \
! RUN: | FileCheck %s --check-prefix=ERR

! RANGE-NOT: -complex-range=
! FULL: -complex-range=full
! IMPRVD: -complex-range=improved
! BASIC: -complex-range=basic

! ERR: error: unsupported argument 'foo' to option '-fcomplex-arithmetic='
Loading
Loading