From 0237f1b998fab69ce365f0ebc79f0ecfa4294744 Mon Sep 17 00:00:00 2001 From: Matt Arsenault Date: Tue, 28 Nov 2023 23:38:50 +0900 Subject: [PATCH] clang: Add pragma clang fp reciprocal (#68267) 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 --- clang/docs/LanguageExtensions.rst | 16 +++ clang/docs/ReleaseNotes.rst | 2 + .../clang/Basic/DiagnosticParseKinds.td | 3 +- .../clang/Basic/DiagnosticSemaKinds.td | 2 +- clang/include/clang/Basic/PragmaKinds.h | 8 ++ clang/include/clang/Sema/Sema.h | 5 +- clang/lib/Parse/ParsePragma.cpp | 51 ++++--- clang/lib/Sema/SemaAttr.cpp | 18 ++- clang/test/CodeGen/fp-reciprocal-pragma.cpp | 130 ++++++++++++++++++ clang/test/Parser/pragma-fp-contract.c | 15 ++ clang/test/Parser/pragma-fp.cpp | 4 +- .../test/Sema/eval-method-with-unsafe-math.c | 32 +++++ 12 files changed, 256 insertions(+), 30 deletions(-) create mode 100644 clang/test/CodeGen/fp-reciprocal-pragma.cpp diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst index 294210c6ac140..8e01ef6cbb399 100644 --- a/clang/docs/LanguageExtensions.rst +++ b/clang/docs/LanguageExtensions.rst @@ -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 diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 7c909ac3cab64..e2e8ee8d76d2e 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -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 ------------------ diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td index 2fd7165a42285..21fe6066d5876 100644 --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -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">; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 6e0ccdf972066..020ff6387c280 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -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">, diff --git a/clang/include/clang/Basic/PragmaKinds.h b/clang/include/clang/Basic/PragmaKinds.h index 176bbc9ac7caa..42f049f7323d2 100644 --- a/clang/include/clang/Basic/PragmaKinds.h +++ b/clang/include/clang/Basic/PragmaKinds.h @@ -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 diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 7579a3256bc37..5d786fd5fb9a2 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -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 diff --git a/clang/lib/Parse/ParsePragma.cpp b/clang/lib/Parse/ParsePragma.cpp index d3fb7fcc8dfac..efdf7c90f977f 100644 --- a/clang/lib/Parse/ParsePragma.cpp +++ b/clang/lib/Parse/ParsePragma.cpp @@ -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 ContractValue; std::optional ReassociateValue; + std::optional ReciprocalValue; std::optional ExceptionsValue; std::optional EvalMethodValue; }; @@ -3261,12 +3261,12 @@ void PragmaFPHandler::HandlePragma(Preprocessor &PP, IdentifierInfo *OptionInfo = Tok.getIdentifierInfo(); auto FlagKind = - llvm::StringSwitch>( - OptionInfo->getName()) - .Case("contract", TokFPAnnotValue::Contract) - .Case("reassociate", TokFPAnnotValue::Reassociate) - .Case("exceptions", TokFPAnnotValue::Exceptions) - .Case("eval_method", TokFPAnnotValue::EvalMethod) + llvm::StringSwitch>(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) @@ -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) { @@ -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>( II->getName()) @@ -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>( - 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>( + 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>( II->getName()) @@ -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>( II->getName()) @@ -3437,9 +3438,15 @@ void Parser::HandlePragmaFP() { reinterpret_cast(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); diff --git a/clang/lib/Sema/SemaAttr.cpp b/clang/lib/Sema/SemaAttr.cpp index 6dadf01ead444..79271c8726273 100644 --- a/clang/lib/Sema/SemaAttr.cpp +++ b/clang/lib/Sema/SemaAttr.cpp @@ -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. @@ -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()); } diff --git a/clang/test/CodeGen/fp-reciprocal-pragma.cpp b/clang/test/CodeGen/fp-reciprocal-pragma.cpp new file mode 100644 index 0000000000000..db93550301bf2 --- /dev/null +++ b/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 +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(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); +} diff --git a/clang/test/Parser/pragma-fp-contract.c b/clang/test/Parser/pragma-fp-contract.c index 3230a23792af3..788fffc00d70d 100644 --- a/clang/test/Parser/pragma-fp-contract.c +++ b/clang/test/Parser/pragma-fp-contract.c @@ -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; +} diff --git a/clang/test/Parser/pragma-fp.cpp b/clang/test/Parser/pragma-fp.cpp index 0377a1b259f46..737aee03f4bb0 100644 --- a/clang/test/Parser/pragma-fp.cpp +++ b/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; diff --git a/clang/test/Sema/eval-method-with-unsafe-math.c b/clang/test/Sema/eval-method-with-unsafe-math.c index b68dc6e7505d3..eb61e1d9f3e63 100644 --- a/clang/test/Sema/eval-method-with-unsafe-math.c +++ b/clang/test/Sema/eval-method-with-unsafe-math.c @@ -54,3 +54,35 @@ float f4(float a, float b, float c) { #pragma clang fp reassociate(on) return (a * c) - (b * c); } + +float f4_reciprocal(float a, float b, float c) { +#pragma clang fp eval_method(double) + // CHECK-FUNC: '#pragma clang fp eval_method' cannot be used with option 'fapprox-func' + // CHECK-ASSOC: '#pragma clang fp eval_method' cannot be used with option 'mreassociate' + // CHECK-RECPR: '#pragma clang fp eval_method' cannot be used with option 'freciprocal' + // CHECK-PRGM: '#pragma clang fp eval_method' cannot be used with '#pragma clang fp reciprocal' +#pragma clang fp reciprocal(on) + return (a * c) / (b * c); +} + +float f4_reciprocal_reassociate(float a, float b, float c) { +#pragma clang fp eval_method(double) + // CHECK-FUNC: '#pragma clang fp eval_method' cannot be used with option 'fapprox-func' + // CHECK-ASSOC: '#pragma clang fp eval_method' cannot be used with option 'mreassociate' + // CHECK-RECPR: '#pragma clang fp eval_method' cannot be used with option 'freciprocal' + // CHECK-PRGM: '#pragma clang fp eval_method' cannot be used with '#pragma clang fp reassociate' + // CHECK-PRGM: '#pragma clang fp eval_method' cannot be used with '#pragma clang fp reciprocal' +#pragma clang fp reciprocal(on) reassociate(on) + return (a * c) / (b * c); +} + +float f2_reciprocal(float a, float b, float c) { + // CHECK-FFP-OPT: option 'ffp-eval-method' cannot be used with '#pragma clang fp reciprocal' +#pragma clang fp reciprocal(on) + return (a + b) / c; +} + +float f3_reciprocal(float a, float b, float c) { +#pragma clang fp reciprocal(off) + return (a - b) / c; +}