Skip to content

Commit

Permalink
clang: Add pragma clang fp reciprocal (#68267)
Browse files Browse the repository at this point in the history
Just follow along with the reassociate pragma. This allows locally
setting the arcp fast math flag. Previously you could only access this
through the global -freciprocal-math.

Fixes #64798
  • Loading branch information
arsenm committed Nov 28, 2023
1 parent ced5c1c commit 0237f1b
Show file tree
Hide file tree
Showing 12 changed files with 256 additions and 30 deletions.
16 changes: 16 additions & 0 deletions clang/docs/LanguageExtensions.rst
Expand Up @@ -4629,6 +4629,22 @@ The pragma can take two values: ``on`` and ``off``.
float v = t + z;
}
``#pragma clang fp reciprocal`` allows control over using reciprocal
approximations in floating point expressions. When enabled, this
pragma allows the expression ``x / y`` to be approximated as ``x *
(1.0 / y)``. This pragma can be used to disable reciprocal
approximation when it is otherwise enabled for the translation unit
with the ``-freciprocal-math`` flag or other fast-math options. The
pragma can take two values: ``on`` and ``off``.
.. code-block:: c++
float f(float x, float y)
{
// Enable floating point reciprocal approximation
#pragma clang fp reciprocal(on)
return x / y;
}
``#pragma clang fp contract`` specifies whether the compiler should
contract a multiply and an addition (or subtraction) into a fused FMA
Expand Down
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Expand Up @@ -228,6 +228,8 @@ Non-comprehensive list of changes in this release
* ``__builtin_classify_type()`` now classifies ``_BitInt`` values as the return value ``18``
and vector types as return value ``19``, to match GCC 14's behavior.

* Added ``#pragma clang fp reciprocal``.

New Compiler Flags
------------------

Expand Down
3 changes: 2 additions & 1 deletion clang/include/clang/Basic/DiagnosticParseKinds.td
Expand Up @@ -1594,12 +1594,13 @@ def note_pragma_loop_invalid_vectorize_option : Note<
"vectorize_width(X, scalable) where X is an integer, or vectorize_width('fixed' or 'scalable')">;

def err_pragma_fp_invalid_option : Error<
"%select{invalid|missing}0 option%select{ %1|}0; expected 'contract', 'reassociate' or 'exceptions'">;
"%select{invalid|missing}0 option%select{ %1|}0; expected 'contract', 'reassociate', 'reciprocal', or 'exceptions'">;
def err_pragma_fp_invalid_argument : Error<
"unexpected argument '%0' to '#pragma clang fp %1'; expected "
"%select{"
"'fast' or 'on' or 'off'|"
"'on' or 'off'|"
"'on' or 'off'|"
"'ignore', 'maytrap' or 'strict'|"
"'source', 'double' or 'extended'}2">;

Expand Down
2 changes: 1 addition & 1 deletion clang/include/clang/Basic/DiagnosticSemaKinds.td
Expand Up @@ -6759,7 +6759,7 @@ def warn_floatingpoint_eq : Warning<

def err_setting_eval_method_used_in_unsafe_context : Error <
"%select{'#pragma clang fp eval_method'|option 'ffp-eval-method'}0 cannot be used with "
"%select{option 'fapprox-func'|option 'mreassociate'|option 'freciprocal'|option 'ffp-eval-method'|'#pragma clang fp reassociate'}1">;
"%select{option 'fapprox-func'|option 'mreassociate'|option 'freciprocal'|option 'ffp-eval-method'|'#pragma clang fp reassociate'|'#pragma clang fp reciprocal'}1">;

def warn_remainder_division_by_zero : Warning<
"%select{remainder|division}0 by zero is undefined">,
Expand Down
8 changes: 8 additions & 0 deletions clang/include/clang/Basic/PragmaKinds.h
Expand Up @@ -34,6 +34,14 @@ enum PragmaFloatControlKind {
PFC_Push, // #pragma float_control(push)
PFC_Pop // #pragma float_control(pop)
};

enum PragmaFPKind {
PFK_Contract, // #pragma clang fp contract
PFK_Reassociate, // #pragma clang fp reassociate
PFK_Reciprocal, // #pragma clang fp reciprocal
PFK_Exceptions, // #pragma clang fp exceptions
PFK_EvalMethod // #pragma clang fp eval_method
};
}

#endif
5 changes: 4 additions & 1 deletion clang/include/clang/Sema/Sema.h
Expand Up @@ -11002,7 +11002,10 @@ class Sema final {

/// Called on well formed
/// \#pragma clang fp reassociate
void ActOnPragmaFPReassociate(SourceLocation Loc, bool IsEnabled);
/// or
/// \#pragma clang fp reciprocal
void ActOnPragmaFPValueChangingOption(SourceLocation Loc, PragmaFPKind Kind,
bool IsEnabled);

/// ActOnPragmaFenvAccess - Called on well formed
/// \#pragma STDC FENV_ACCESS
Expand Down
51 changes: 29 additions & 22 deletions clang/lib/Parse/ParsePragma.cpp
Expand Up @@ -3233,11 +3233,11 @@ void PragmaOptimizeHandler::HandlePragma(Preprocessor &PP,
namespace {
/// Used as the annotation value for tok::annot_pragma_fp.
struct TokFPAnnotValue {
enum FlagKinds { Contract, Reassociate, Exceptions, EvalMethod };
enum FlagValues { On, Off, Fast };

std::optional<LangOptions::FPModeKind> ContractValue;
std::optional<LangOptions::FPModeKind> ReassociateValue;
std::optional<LangOptions::FPModeKind> ReciprocalValue;
std::optional<LangOptions::FPExceptionModeKind> ExceptionsValue;
std::optional<LangOptions::FPEvalMethodKind> EvalMethodValue;
};
Expand All @@ -3261,12 +3261,12 @@ void PragmaFPHandler::HandlePragma(Preprocessor &PP,
IdentifierInfo *OptionInfo = Tok.getIdentifierInfo();

auto FlagKind =
llvm::StringSwitch<std::optional<TokFPAnnotValue::FlagKinds>>(
OptionInfo->getName())
.Case("contract", TokFPAnnotValue::Contract)
.Case("reassociate", TokFPAnnotValue::Reassociate)
.Case("exceptions", TokFPAnnotValue::Exceptions)
.Case("eval_method", TokFPAnnotValue::EvalMethod)
llvm::StringSwitch<std::optional<PragmaFPKind>>(OptionInfo->getName())
.Case("contract", PFK_Contract)
.Case("reassociate", PFK_Reassociate)
.Case("exceptions", PFK_Exceptions)
.Case("eval_method", PFK_EvalMethod)
.Case("reciprocal", PFK_Reciprocal)
.Default(std::nullopt);
if (!FlagKind) {
PP.Diag(Tok.getLocation(), diag::err_pragma_fp_invalid_option)
Expand All @@ -3282,7 +3282,7 @@ void PragmaFPHandler::HandlePragma(Preprocessor &PP,
}
PP.Lex(Tok);
bool isEvalMethodDouble =
Tok.is(tok::kw_double) && FlagKind == TokFPAnnotValue::EvalMethod;
Tok.is(tok::kw_double) && FlagKind == PFK_EvalMethod;

// Don't diagnose if we have an eval_metod pragma with "double" kind.
if (Tok.isNot(tok::identifier) && !isEvalMethodDouble) {
Expand All @@ -3293,7 +3293,7 @@ void PragmaFPHandler::HandlePragma(Preprocessor &PP,
}
const IdentifierInfo *II = Tok.getIdentifierInfo();

if (FlagKind == TokFPAnnotValue::Contract) {
if (FlagKind == PFK_Contract) {
AnnotValue->ContractValue =
llvm::StringSwitch<std::optional<LangOptions::FPModeKind>>(
II->getName())
Expand All @@ -3306,19 +3306,20 @@ void PragmaFPHandler::HandlePragma(Preprocessor &PP,
<< PP.getSpelling(Tok) << OptionInfo->getName() << *FlagKind;
return;
}
} else if (FlagKind == TokFPAnnotValue::Reassociate) {
AnnotValue->ReassociateValue =
llvm::StringSwitch<std::optional<LangOptions::FPModeKind>>(
II->getName())
.Case("on", LangOptions::FPModeKind::FPM_On)
.Case("off", LangOptions::FPModeKind::FPM_Off)
.Default(std::nullopt);
if (!AnnotValue->ReassociateValue) {
} else if (FlagKind == PFK_Reassociate || FlagKind == PFK_Reciprocal) {
auto &Value = FlagKind == PFK_Reassociate ? AnnotValue->ReassociateValue
: AnnotValue->ReciprocalValue;
Value = llvm::StringSwitch<std::optional<LangOptions::FPModeKind>>(
II->getName())
.Case("on", LangOptions::FPModeKind::FPM_On)
.Case("off", LangOptions::FPModeKind::FPM_Off)
.Default(std::nullopt);
if (!Value) {
PP.Diag(Tok.getLocation(), diag::err_pragma_fp_invalid_argument)
<< PP.getSpelling(Tok) << OptionInfo->getName() << *FlagKind;
return;
}
} else if (FlagKind == TokFPAnnotValue::Exceptions) {
} else if (FlagKind == PFK_Exceptions) {
AnnotValue->ExceptionsValue =
llvm::StringSwitch<std::optional<LangOptions::FPExceptionModeKind>>(
II->getName())
Expand All @@ -3331,7 +3332,7 @@ void PragmaFPHandler::HandlePragma(Preprocessor &PP,
<< PP.getSpelling(Tok) << OptionInfo->getName() << *FlagKind;
return;
}
} else if (FlagKind == TokFPAnnotValue::EvalMethod) {
} else if (FlagKind == PFK_EvalMethod) {
AnnotValue->EvalMethodValue =
llvm::StringSwitch<std::optional<LangOptions::FPEvalMethodKind>>(
II->getName())
Expand Down Expand Up @@ -3437,9 +3438,15 @@ void Parser::HandlePragmaFP() {
reinterpret_cast<TokFPAnnotValue *>(Tok.getAnnotationValue());

if (AnnotValue->ReassociateValue)
Actions.ActOnPragmaFPReassociate(Tok.getLocation(),
*AnnotValue->ReassociateValue ==
LangOptions::FPModeKind::FPM_On);
Actions.ActOnPragmaFPValueChangingOption(
Tok.getLocation(), PFK_Reassociate,
*AnnotValue->ReassociateValue == LangOptions::FPModeKind::FPM_On);

if (AnnotValue->ReciprocalValue)
Actions.ActOnPragmaFPValueChangingOption(
Tok.getLocation(), PFK_Reciprocal,
*AnnotValue->ReciprocalValue == LangOptions::FPModeKind::FPM_On);

if (AnnotValue->ContractValue)
Actions.ActOnPragmaFPContract(Tok.getLocation(),
*AnnotValue->ContractValue);
Expand Down
18 changes: 15 additions & 3 deletions clang/lib/Sema/SemaAttr.cpp
Expand Up @@ -1285,7 +1285,8 @@ void Sema::ActOnPragmaFPContract(SourceLocation Loc,
CurFPFeatures = NewFPFeatures.applyOverrides(getLangOpts());
}

void Sema::ActOnPragmaFPReassociate(SourceLocation Loc, bool IsEnabled) {
void Sema::ActOnPragmaFPValueChangingOption(SourceLocation Loc,
PragmaFPKind Kind, bool IsEnabled) {
if (IsEnabled) {
// For value unsafe context, combining this pragma with eval method
// setting is not recommended. See comment in function FixupInvocation#506.
Expand All @@ -1301,10 +1302,21 @@ void Sema::ActOnPragmaFPReassociate(SourceLocation Loc, bool IsEnabled) {
Reason = 0;
if (Reason != -1)
Diag(Loc, diag::err_setting_eval_method_used_in_unsafe_context)
<< Reason << 4;
<< Reason << (Kind == PFK_Reassociate ? 4 : 5);
}

FPOptionsOverride NewFPFeatures = CurFPFeatureOverrides();
NewFPFeatures.setAllowFPReassociateOverride(IsEnabled);
switch (Kind) {
case PFK_Reassociate:
NewFPFeatures.setAllowFPReassociateOverride(IsEnabled);
break;
case PFK_Reciprocal:
NewFPFeatures.setAllowReciprocalOverride(IsEnabled);
break;
default:
llvm_unreachable("unhandled value changing pragma fp");
}

FpPragmaStack.Act(Loc, PSK_Set, StringRef(), NewFPFeatures);
CurFPFeatures = NewFPFeatures.applyOverrides(getLangOpts());
}
Expand Down
130 changes: 130 additions & 0 deletions clang/test/CodeGen/fp-reciprocal-pragma.cpp
@@ -0,0 +1,130 @@
// RUN: %clang_cc1 -O3 -triple %itanium_abi_triple -emit-llvm -o - %s | FileCheck -check-prefixes=CHECK,DEFAULT %s
// RUN: %clang_cc1 -O3 -triple %itanium_abi_triple -freciprocal-math -emit-llvm -o - %s | FileCheck -check-prefixes=CHECK,FLAG %s

float base(float a, float b, float c) {
// CHECK-LABEL: _Z4basefff
// FLAG: %[[A:.+]] = fdiv arcp float %b, %c
// FLAG: %[[M:.+]] = fdiv arcp float %[[A]], %b
// FLAG-NEXT: fadd arcp float %[[M]], %c

// DEFAULT: %[[A:.+]] = fdiv float %b, %c
// DEFAULT: %[[M:.+]] = fdiv float %[[A]], %b
// DEFAULT-NEXT: fadd float %[[M]], %c
a = b / c;
return a / b + c;
}

// Simple case
float fp_recip_simple(float a, float b, float c) {
// CHECK-LABEL: _Z15fp_recip_simplefff
// CHECK: %[[A:.+]] = fdiv arcp float %b, %c
// CHECK: %[[M:.+]] = fdiv arcp float %[[A]], %b
// CHECK-NEXT: fadd arcp float %[[M]], %c
#pragma clang fp reciprocal(on)
a = b / c;
return a / b + c;
}

// Test interaction with -freciprocal-math
float fp_recip_disable(float a, float b, float c) {
// CHECK-LABEL: _Z16fp_recip_disablefff
// CHECK: %[[A:.+]] = fdiv float %b, %c
// CHECK: %[[M:.+]] = fdiv float %[[A]], %b
// CHECK-NEXT: fadd float %[[M]], %c
#pragma clang fp reciprocal(off)
a = b / c;
return a / b + c;
}

float fp_recip_with_reassoc_simple(float a, float b, float c) {
// CHECK-LABEL: _Z28fp_recip_with_reassoc_simplefff
// CHECK: %[[A:.+]] = fmul reassoc arcp float %b, %c
// CHECK: %[[M:.+]] = fdiv reassoc arcp float %b, %[[A]]
// CHECK-NEXT: fadd reassoc arcp float %[[M]], %c
#pragma clang fp reciprocal(on) reassociate(on)
a = b / c;
return a / b + c;
}

// arcp pragma should only apply to its scope
float fp_recip_scoped(float a, float b, float c) {
// CHECK-LABEL: _Z15fp_recip_scopedfff
// DEFAULT: %[[M:.+]] = fdiv float %a, %b
// DEFAULT-NEXT: fadd float %[[M]], %c
// FLAG: %[[M:.+]] = fdiv arcp float %a, %b
// FLAG-NEXT: fadd arcp float %[[M]], %c
{
#pragma clang fp reciprocal(on)
}
return a / b + c;
}

// arcp pragma should apply to templates as well
class Foo {};
Foo operator+(Foo, Foo);
template <typename T>
T template_recip(T a, T b, T c) {
#pragma clang fp reciprocal(on)
return ((a / b) - c) + c;
}

float fp_recip_template(float a, float b, float c) {
// CHECK-LABEL: _Z17fp_recip_templatefff
// CHECK: %[[A1:.+]] = fdiv arcp float %a, %b
// CHECK-NEXT: %[[A2:.+]] = fsub arcp float %[[A1]], %c
// CHECK-NEXT: fadd arcp float %[[A2]], %c
return template_recip<float>(a, b, c);
}

// File Scoping should work across functions
#pragma clang fp reciprocal(on)
float fp_file_scope_on(float a, float b, float c) {
// CHECK-LABEL: _Z16fp_file_scope_onfff
// CHECK: %[[M1:.+]] = fdiv arcp float %a, %c
// CHECK-NEXT: %[[M2:.+]] = fdiv arcp float %b, %c
// CHECK-NEXT: fadd arcp float %[[M1]], %[[M2]]
return (a / c) + (b / c);
}

// Inner pragma has precedence
float fp_file_scope_stop(float a, float b, float c) {
// CHECK-LABEL: _Z18fp_file_scope_stopfff
// CHECK: %[[A:.+]] = fdiv arcp float %a, %a
// CHECK: %[[M1:.+]] = fdiv float %[[A]], %c
// CHECK-NEXT: %[[M2:.+]] = fdiv float %b, %c
// CHECK-NEXT: fsub float %[[M1]], %[[M2]]
a = a / a;
{
#pragma clang fp reciprocal(off)
return (a / c) - (b / c);
}
}

#pragma clang fp reciprocal(off)
float fp_recip_off(float a, float b, float c) {
// CHECK-LABEL: _Z12fp_recip_offfff
// CHECK: %[[D1:.+]] = fdiv float %a, %c
// CHECK-NEXT: %[[D2:.+]] = fdiv float %b, %c
// CHECK-NEXT: fadd float %[[D1]], %[[D2]]
return (a / c) + (b / c);
}

// Takes latest flag
float fp_recip_many(float a, float b, float c) {
// CHECK-LABEL: _Z13fp_recip_manyfff
// CHECK: %[[D1:.+]] = fdiv arcp float %a, %c
// CHECK-NEXT: %[[D2:.+]] = fdiv arcp float %b, %c
// CHECK-NEXT: fadd arcp float %[[D1]], %[[D2]]
#pragma clang fp reciprocal(off) reciprocal(on)
return (a / c) + (b / c);
}

// Pragma does not propagate through called functions
float helper_func(float a, float b, float c) { return a + b + c; }
float fp_recip_call_helper(float a, float b, float c) {
// CHECK-LABEL: _Z20fp_recip_call_helperfff
// CHECK: %[[S1:.+]] = fadd float %a, %b
// CHECK-NEXT: fadd float %[[S1]], %c
#pragma clang fp reciprocal(on)
return helper_func(a, b, c);
}
15 changes: 15 additions & 0 deletions clang/test/Parser/pragma-fp-contract.c
Expand Up @@ -38,3 +38,18 @@ float fp_reassoc_no_fast(float a, float b) {
#pragma clang fp reassociate(fast)
return a - b;
}

float fp_recip_fail(float a, float b) {
// CHECK-LABEL: fp_recip_fail
// expected-error@+2{{'#pragma clang fp' can only appear at file scope or at the start of a compound statement}}
float c = a + b;
#pragma clang fp reciprocal(off)
return c - b;
}

float fp_recip_no_fast(float a, float b) {
// CHECK-LABEL: fp_recip_no_fast
// expected-error@+1{{unexpected argument 'fast' to '#pragma clang fp reciprocal'; expected 'on' or 'off'}}
#pragma clang fp reciprocal(fast)
return a - b;
}
4 changes: 2 additions & 2 deletions clang/test/Parser/pragma-fp.cpp
@@ -1,14 +1,14 @@
// RUN: %clang_cc1 -std=c++11 -verify %s

void test_0(int *List, int Length) {
/* expected-error@+1 {{missing option; expected 'contract', 'reassociate' or 'exceptions'}} */
/* expected-error@+1 {{missing option; expected 'contract', 'reassociate', 'reciprocal', or 'exceptions'}} */
#pragma clang fp
for (int i = 0; i < Length; i++) {
List[i] = i;
}
}
void test_1(int *List, int Length) {
/* expected-error@+1 {{invalid option 'blah'; expected 'contract', 'reassociate' or 'exceptions'}} */
/* expected-error@+1 {{invalid option 'blah'; expected 'contract', 'reassociate', 'reciprocal', or 'exceptions'}} */
#pragma clang fp blah
for (int i = 0; i < Length; i++) {
List[i] = i;
Expand Down

0 comments on commit 0237f1b

Please sign in to comment.