Skip to content

Implementation of '#pragma STDC FENV_ROUND' #89617

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 3 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
9 changes: 9 additions & 0 deletions clang/include/clang/AST/Stmt.h
Original file line number Diff line number Diff line change
Expand Up @@ -1658,6 +1658,15 @@ class CompoundStmt final
return *getTrailingObjects<FPOptionsOverride>();
}

/// Get FPOptions inside this statement. They may differ from the outer
/// options due to pragmas.
/// \param CurFPOptions FPOptions outside this statement.
FPOptions getNewFPOptions(FPOptions CurFPOptions) const {
return hasStoredFPFeatures()
? getStoredFPFeatures().applyOverrides(CurFPOptions)
: CurFPOptions;
}

using body_iterator = Stmt **;
using body_range = llvm::iterator_range<body_iterator>;

Expand Down
6 changes: 0 additions & 6 deletions clang/include/clang/Basic/DiagnosticParseKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -1263,12 +1263,6 @@ def err_pragma_file_or_compound_scope : Error<
// - #pragma stdc unknown
def ext_stdc_pragma_ignored : ExtWarn<"unknown pragma in STDC namespace">,
InGroup<UnknownPragmas>;
// The C standard 7.6.1p2 says "The [FENV_ACCESS] pragma shall occur either
// outside external declarations or preceding all explicit declarations and
// statements inside a compound statement.
def warn_stdc_fenv_round_not_supported :
Warning<"pragma STDC FENV_ROUND is not supported">,
InGroup<UnknownPragmas>;
def warn_stdc_unknown_rounding_mode : Warning<
"invalid or unsupported rounding mode in '#pragma STDC FENV_ROUND' - ignored">,
InGroup<IgnoredPragmas>;
Expand Down
6 changes: 6 additions & 0 deletions clang/include/clang/Basic/LangOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,12 @@ class FPOptions {
getAllowFEnvAccess();
}

/// Checks if the rounding mode is unknown at compile-time.
bool isRoundingModeDynamic() const {
return (getConstRoundingMode() == RoundingMode::Dynamic) &&
(getAllowFEnvAccess() || getRoundingMath());
}

RoundingMode getRoundingMode() const {
RoundingMode RM = getConstRoundingMode();
if (RM == RoundingMode::Dynamic) {
Expand Down
8 changes: 8 additions & 0 deletions clang/include/clang/Basic/TargetInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -1232,6 +1232,14 @@ class TargetInfo : public TransferrableTargetInfo,
return true;
}

/// Returns true, if an operations that depends on rounding mode can be
/// implemented without changing FP environment. In this case the rounding
/// mode is encoded in the bits of implementing instruction.
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure we actually have any (non-target-specific) IR constructs at the moment that actually implement static rounding mode. The documentation for constrained intrinsics says:

For values other than “round.dynamic” optimization passes may assume that the actual runtime rounding mode (as defined in a target-specific manner) matches the specified rounding mode, but this is not guaranteed.

It's also the case that static rounding mode may be a less-than-global decision. X86 AVX512/AVX10 has static rounding mode, which is a subtarget consideration, but even then, it's not entirely clear that they would be absolutely preferred.

virtual bool hasStaticRounding() const {
// Most supported targets require setting hardware register to use
// particular rounding mode.
return false;
}
/// Returns the target triple of the primary target.
const llvm::Triple &getTriple() const {
return Triple;
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/Basic/Targets/RISCV.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ class RISCVTargetInfo : public TargetInfo {

bool hasBFloat16Type() const override { return true; }

bool hasStaticRounding() const override { return true; }

CallingConvCheckResult checkCallingConvention(CallingConv CC) const override;

bool useFP16ConversionIntrinsics() const override {
Expand Down
65 changes: 65 additions & 0 deletions clang/lib/CodeGen/CGCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "clang/CodeGen/CGFunctionInfo.h"
#include "clang/CodeGen/SwiftCallingConv.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/Analysis/ValueTracking.h"
#include "llvm/IR/Assumptions.h"
#include "llvm/IR/AttributeMask.h"
Expand Down Expand Up @@ -5693,6 +5694,9 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
AllocAlignAttrEmitter AllocAlignAttrEmitter(*this, TargetDecl, CallArgs);
Attrs = AllocAlignAttrEmitter.TryEmitAsCallSiteAttribute(Attrs);

// Prepare execution environment.
setRoundingModeForCall(Callee);

// Emit the actual call/invoke instruction.
llvm::CallBase *CI;
if (!InvokeDest) {
Expand Down Expand Up @@ -5846,6 +5850,9 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
// lexical order, so deactivate it and run it manually here.
CallArgs.freeArgumentMemory(*this);

// Restore execution environment.
restoreRoundingModeAfterCall();

// Extract the return value.
RValue Ret = [&] {
switch (RetAI.getKind()) {
Expand Down Expand Up @@ -5980,6 +5987,64 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
return Ret;
}

static bool endsWithRoundingModeSuffix(StringRef FuncName) {
size_t Underscore = FuncName.find_last_of("_");
if (Underscore == StringRef::npos || Underscore < 2)
return false;
StringRef Suffix = FuncName.substr(Underscore + 1);
static const StringRef RMSuffixes[] = {"rtz", "rte", "rtp", "rtn", "rhaz",
"rz", "rn", "ru", "rd"};
for (auto RM : RMSuffixes) {
if (Suffix == RM)
return true;
}
return false;
}

bool CodeGenFunction::requiresDynamicRounding(const CGCallee &Callee) {
if (Callee.isOrdinary()) {
const Decl *CalleeDecl = Callee.getAbstractInfo().getCalleeDecl().getDecl();
if (const FunctionDecl *FD = dyn_cast_or_null<FunctionDecl>(CalleeDecl)) {
IdentifierInfo *FuncNameII = FD->getDeclName().getAsIdentifierInfo();
if (FuncNameII) {
StringRef FuncName = FuncNameII->getName();
// If a reserved identifier ends with rounding mode suffix preceded by
// underscore, this function does not need the previous dynamic rounding
// mode to be set.
Comment on lines +6011 to +6013
Copy link
Contributor

Choose a reason for hiding this comment

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

Where is this rule coming from?

if (isReservedInAllContexts(
FuncNameII->isReserved(getContext().getLangOpts()))) {
if (endsWithRoundingModeSuffix(FuncName))
return false;
}
}
}
}
return true;
}

/// Sets dynamic rounding mode for the function called in the region where
/// pragma FENV_ROUND is in effect.
void CodeGenFunction::setRoundingModeForCall(const CGCallee &Callee) {
if (Target.hasStaticRounding() || Callee.isBuiltin() ||
!requiresDynamicRounding(Callee))
return;
if (!CurrentRoundingIsStatic || !DynamicRoundingMode)
return;
Builder.CreateCall(CGM.getIntrinsic(llvm::Intrinsic::set_rounding),
DynamicRoundingMode);
CurrentRoundingIsStatic = false;
Comment on lines +6028 to +6035
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not seeing logic here to effect the proper handling of, e.g., sqrt. Is this planned in a future patch? If so, add in a TODO note.

}

void CodeGenFunction::restoreRoundingModeAfterCall() {
if (Target.hasStaticRounding() || CurFPFeatures.isRoundingModeDynamic())
return;
if (CurrentRoundingIsStatic || !StaticRoundingMode)
return;
Builder.CreateCall(CGM.getIntrinsic(llvm::Intrinsic::set_rounding),
StaticRoundingMode);
CurrentRoundingIsStatic = true;
}

CGCallee CGCallee::prepareConcreteCallee(CodeGenFunction &CGF) const {
if (isVirtual()) {
const CallExpr *CE = getVirtualCallExpr();
Expand Down
62 changes: 62 additions & 0 deletions clang/lib/CodeGen/CGStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,62 @@ bool CodeGenFunction::EmitSimpleStmt(const Stmt *S,
return true;
}

namespace {
/// Cleanup action that restores floating-point control modes upon leaving
/// a scope.
struct FPControlModesCleanup final : EHScopeStack::Cleanup {
llvm::Value *PreviousModes;
FPControlModesCleanup(llvm::Value *M) : PreviousModes(M) {}
void Emit(CodeGenFunction &CGF, Flags flags) override {
CGF.Builder.CreateIntrinsic(llvm::Intrinsic::set_rounding, {},
{PreviousModes});
}
};
} // namespace

void CodeGenFunction::emitSetFPControlModes(FPOptions NewFP, FPOptions OldFP) {
if (NewFP == OldFP)
return;

// For now only rounding mode is handled.

if (Target.hasStaticRounding())
return;

// If the new rounding mode is unknown in compile-time, it means that the
// compound statement contains `#pragma STDC FENV_ACCESS ON`. In this case all
// manipulations on FP environment, including setting and restoring control
// modes are made by the user.
if (NewFP.isRoundingModeDynamic())
return;

llvm::RoundingMode OldConstRM = OldFP.getConstRoundingMode();
llvm::RoundingMode NewConstRM = NewFP.getConstRoundingMode();
if (OldConstRM == NewConstRM)
return;

llvm::RoundingMode OldRM = OldFP.getRoundingMode();
if (OldRM == NewConstRM)
return;

if (OldFP.isRoundingModeDynamic()) {
llvm::Function *FGetRound = CGM.getIntrinsic(llvm::Intrinsic::get_rounding);
DynamicRoundingMode = Builder.CreateCall(FGetRound);
} else {
DynamicRoundingMode =
llvm::ConstantInt::get(Int32Ty, static_cast<uint64_t>(OldRM));
}

llvm::RoundingMode NewRM = NewFP.getRoundingMode();
StaticRoundingMode =
llvm::ConstantInt::get(Int32Ty, static_cast<uint64_t>(NewRM));
Builder.CreateIntrinsic(llvm::Intrinsic::set_rounding, {},
StaticRoundingMode);
EHStack.pushCleanup<FPControlModesCleanup>(NormalAndEHCleanup,
DynamicRoundingMode);
CurrentRoundingIsStatic = true;
}

/// EmitCompoundStmt - Emit a compound statement {..} node. If GetLast is true,
/// this captures the expression result of the last sub-statement and returns it
/// (for use by the statement expression extension).
Expand All @@ -509,6 +565,12 @@ CodeGenFunction::EmitCompoundStmtWithoutScope(const CompoundStmt &S,
assert((!GetLast || (GetLast && ExprResult)) &&
"If GetLast is true then the CompoundStmt must have a StmtExprResult");

// Optionally set up the new FP environment, if the compound statement
// contains a pragma that modifies it.
FPOptions NewFP = S.getNewFPOptions(CurFPFeatures);
CGFPOptionsRAII SavedFPFeatues(*this, NewFP);
emitSetFPControlModes(NewFP, SavedFPFeatues.getOldFPOptions());

Address RetAlloca = Address::invalid();

for (auto *CurStmt : S.body()) {
Expand Down
6 changes: 6 additions & 0 deletions clang/lib/CodeGen/CodeGenFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ CodeGenFunction::CGFPOptionsRAII::CGFPOptionsRAII(CodeGenFunction &CGF,
void CodeGenFunction::CGFPOptionsRAII::ConstructorHelper(FPOptions FPFeatures) {
OldFPFeatures = CGF.CurFPFeatures;
CGF.CurFPFeatures = FPFeatures;
OldDynamicRM = CGF.DynamicRoundingMode;
OldStaticRM = CGF.StaticRoundingMode;
OldCurrentRoundingIsStatic = CGF.CurrentRoundingIsStatic;

OldExcept = CGF.Builder.getDefaultConstrainedExcept();
OldRounding = CGF.Builder.getDefaultConstrainedRounding();
Expand Down Expand Up @@ -191,6 +194,9 @@ void CodeGenFunction::CGFPOptionsRAII::ConstructorHelper(FPOptions FPFeatures) {

CodeGenFunction::CGFPOptionsRAII::~CGFPOptionsRAII() {
CGF.CurFPFeatures = OldFPFeatures;
CGF.DynamicRoundingMode = OldDynamicRM;
CGF.StaticRoundingMode = OldStaticRM;
CGF.CurrentRoundingIsStatic = OldCurrentRoundingIsStatic;
CGF.Builder.setDefaultConstrainedExcept(OldExcept);
CGF.Builder.setDefaultConstrainedRounding(OldRounding);
}
Expand Down
16 changes: 16 additions & 0 deletions clang/lib/CodeGen/CodeGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -817,15 +817,23 @@ class CodeGenFunction : public CodeGenTypeCache {
CGFPOptionsRAII(CodeGenFunction &CGF, const Expr *E);
~CGFPOptionsRAII();

FPOptions getOldFPOptions() const { return OldFPFeatures; }

private:
void ConstructorHelper(FPOptions FPFeatures);
CodeGenFunction &CGF;
FPOptions OldFPFeatures;
llvm::Value *OldDynamicRM;
llvm::Value *OldStaticRM;
bool OldCurrentRoundingIsStatic;
llvm::fp::ExceptionBehavior OldExcept;
llvm::RoundingMode OldRounding;
std::optional<CGBuilderTy::FastMathFlagGuard> FMFGuard;
};
FPOptions CurFPFeatures;
llvm::Value *DynamicRoundingMode = nullptr;
llvm::Value *StaticRoundingMode = nullptr;
bool CurrentRoundingIsStatic = false;

public:
/// ObjCEHValueStack - Stack of Objective-C exception values, used for
Expand Down Expand Up @@ -3324,6 +3332,14 @@ class CodeGenFunction : public CodeGenTypeCache {
/// Get the record field index as represented in debug info.
unsigned getDebugInfoFIndex(const RecordDecl *Rec, unsigned FieldIndex);

/// Optionally emit code that sets required floating-point control modes and
/// creates corresponding cleanup action.
void emitSetFPControlModes(FPOptions NewFP, FPOptions OldFP);

void setRoundingModeForCall(const CGCallee &Callee);
void restoreRoundingModeAfterCall();
bool requiresDynamicRounding(const CGCallee &Callee);


//===--------------------------------------------------------------------===//
// Declaration Emission
Expand Down
3 changes: 0 additions & 3 deletions clang/lib/Parse/ParsePragma.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3463,9 +3463,6 @@ void PragmaSTDC_FENV_ROUNDHandler::HandlePragma(Preprocessor &PP,
return;
}

// Until the pragma is fully implemented, issue a warning.
PP.Diag(Tok.getLocation(), diag::warn_stdc_fenv_round_not_supported);

MutableArrayRef<Token> Toks(PP.getPreprocessorAllocator().Allocate<Token>(1),
1);
Toks[0].startToken();
Expand Down
Loading
Loading