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

Merged
Merged
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
41 changes: 7 additions & 34 deletions clang/lib/Driver/ToolChains/Clang.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2740,29 +2740,10 @@ 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)
? ""
: "-fcomplex-arithmetic=" + ComplexRangeKindToStr(Range);
: "-fcomplex-arithmetic=" + complexRangeKindToStr(Range);
}

static void EmitComplexRangeDiag(const Driver &D, std::string str1,
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 Expand Up @@ -2912,7 +2885,7 @@ static void RenderFloatingPointOptions(const ToolChain &TC, const Driver &D,
case options::OPT_fcx_limited_range:
if (GccRangeComplexOption.empty()) {
if (Range != LangOptions::ComplexRangeKind::CX_Basic)
EmitComplexRangeDiag(D, RenderComplexRangeOption(Range),
EmitComplexRangeDiag(D, renderComplexRangeOption(Range),
"-fcx-limited-range");
} else {
if (GccRangeComplexOption != "-fno-cx-limited-range")
Expand All @@ -2924,7 +2897,7 @@ static void RenderFloatingPointOptions(const ToolChain &TC, const Driver &D,
break;
case options::OPT_fno_cx_limited_range:
if (GccRangeComplexOption.empty()) {
EmitComplexRangeDiag(D, RenderComplexRangeOption(Range),
EmitComplexRangeDiag(D, renderComplexRangeOption(Range),
"-fno-cx-limited-range");
} else {
if (GccRangeComplexOption != "-fcx-limited-range" &&
Expand All @@ -2938,7 +2911,7 @@ static void RenderFloatingPointOptions(const ToolChain &TC, const Driver &D,
break;
case options::OPT_fcx_fortran_rules:
if (GccRangeComplexOption.empty())
EmitComplexRangeDiag(D, RenderComplexRangeOption(Range),
EmitComplexRangeDiag(D, renderComplexRangeOption(Range),
"-fcx-fortran-rules");
else
EmitComplexRangeDiag(D, GccRangeComplexOption, "-fcx-fortran-rules");
Expand All @@ -2948,7 +2921,7 @@ static void RenderFloatingPointOptions(const ToolChain &TC, const Driver &D,
break;
case options::OPT_fno_cx_fortran_rules:
if (GccRangeComplexOption.empty()) {
EmitComplexRangeDiag(D, RenderComplexRangeOption(Range),
EmitComplexRangeDiag(D, renderComplexRangeOption(Range),
"-fno-cx-fortran-rules");
} else {
if (GccRangeComplexOption != "-fno-cx-limited-range")
Expand Down Expand Up @@ -3416,12 +3389,12 @@ static void RenderFloatingPointOptions(const ToolChain &TC, const Driver &D,
CmdArgs.push_back("-fno-strict-float-cast-overflow");

if (Range != LangOptions::ComplexRangeKind::CX_None)
ComplexRangeStr = RenderComplexRangeOption(Range);
ComplexRangeStr = renderComplexRangeOption(Range);
if (!ComplexRangeStr.empty()) {
CmdArgs.push_back(Args.MakeArgString(ComplexRangeStr));
if (Args.hasArg(options::OPT_fcomplex_arithmetic_EQ))
CmdArgs.push_back(Args.MakeArgString("-fcomplex-arithmetic=" +
ComplexRangeKindToStr(Range)));
complexRangeKindToStr(Range)));
}
if (Args.hasArg(options::OPT_fcx_limited_range))
CmdArgs.push_back("-fcx-limited-range");
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: {
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
22 changes: 22 additions & 0 deletions flang/docs/ComplexOperations.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,26 @@ The ComplexToStandard dialect does still call into libm for some floating
point math operations, however these don't have the same ABI issues as the
complex libm functions.

The flang driver option `-fcomplex-arithmetic=` allows you to select whether
complex number division is lowered to function calls or to the `complex.div`
operation in the MLIR complex dialect. To avoid the ABI issues mentioned above,
the choice of function calls or the `complex.div` operation is made during the
lowering phase. The behavior of this option is as follows:

- `basic`: Lowers to `complex.div` and is converted to algebraic formulas. No
special handling to avoid overflow. NaN and infinite values are not handled.
- `improved`: Lowers to `complex.div` and is converted to Smith's algorithm. See
SMITH, R. L. Algorithm 116: Complex division. Commun. ACM 5, 8 (1962). This
value offers improved handling for overflow in intermediate calculations, but
overflow may occur. NaN and infinite values are handled.
- `full`: Lowers to a call to runtime library functions. Overflow and non-finite
values are handled by the library implementation. This is the default value.

While [the same option in clang][2] allows specifying `promoted`, this is not
implemented in Flang. Also, in the case of `improved`, clang does not handle NaN
and infinite values, but Flang does. These behavioral differences arise because
the transformation of complex division calculations depends on the implementation
of ComplexToStandard, which may change in the future.

[1]: https://discourse.llvm.org/t/rfc-change-lowering-of-fortran-math-intrinsics/63971
[2]: https://clang.llvm.org/docs/UsersManual.html#cmdoption-fcomplex-arithmetic
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.

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 that it should be moved, but since it is in LangOptions in clang, and not CodeGenOptions, we would need some reviewers from the clang as well. In the interest of not blocking this PR, let's leave things as they are, but it would be good to avoid this duplication.

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
Loading
Loading