llvm-project/flang/runtime/exceptions.cpp
vdonaldson dcb7f44cd6
[flang] Modifications to ieee_support_halting (#120976)
The F23 standard requires that a call to intrinsic module procedure
ieee_support_halting be foldable to a constant at compile time in some
contexts. See for example F23 Clause 10.1.11 [Specification expression]
list item (13), Clause 1.1.12 [Constant expression] list item (11), and
references to specification and constant expressions elsewhere, such as
constraints C1012, C853, and C704.

Some Arm processors allow a user to control processor behavior when an
arithmetic exception is signaled, and some Arm processors do not have
this capability. An Arm executable will run on either type of processor,
so it is effectively unknown at compile time whether or not this support
will be available at runtime. This is in conflict with the standard
requirement.

This patch addresses this conflict by implementing ieee_support_halting
calls on Arm processors to check if this capability is present at
runtime. A call to ieee_support_halting in a constant context, such as
in the specification part of a program unit, will generate a compile
time "cannot be computed as a constant value" error. The expectation is
that such calls are unlikely to appear in production code.

Code generation for other processors will continue to generate a compile
time constant result for ieee_support_halting calls.
2024-12-23 11:07:20 -05:00

124 lines
3.7 KiB
C++

//===-- runtime/exceptions.cpp --------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// Runtime exception support.
#include "flang/Runtime/exceptions.h"
#include "terminator.h"
#include <cfenv>
#if __x86_64__
#include <xmmintrin.h>
#endif
// When not supported, these macro are undefined in cfenv.h,
// set them to zero in that case.
#ifndef FE_INVALID
#define FE_INVALID 0
#endif
#ifndef __FE_DENORM
#define __FE_DENORM 0 // denorm is nonstandard
#endif
#ifndef FE_DIVBYZERO
#define FE_DIVBYZERO 0
#endif
#ifndef FE_OVERFLOW
#define FE_OVERFLOW 0
#endif
#ifndef FE_UNDERFLOW
#define FE_UNDERFLOW 0
#endif
#ifndef FE_INEXACT
#define FE_INEXACT 0
#endif
namespace Fortran::runtime {
extern "C" {
// Map a set of Fortran ieee_arithmetic module exceptions to a libm fenv.h
// excepts value.
uint32_t RTNAME(MapException)(uint32_t excepts) {
Terminator terminator{__FILE__, __LINE__};
static constexpr uint32_t v{FE_INVALID};
static constexpr uint32_t s{__FE_DENORM}; // subnormal
static constexpr uint32_t z{FE_DIVBYZERO};
static constexpr uint32_t o{FE_OVERFLOW};
static constexpr uint32_t u{FE_UNDERFLOW};
static constexpr uint32_t x{FE_INEXACT};
#define vm(p) p, p | v
#define sm(p) vm(p), vm(p | s)
#define zm(p) sm(p), sm(p | z)
#define om(p) zm(p), zm(p | o)
#define um(p) om(p), om(p | u)
#define xm um(0), um(x)
static constexpr uint32_t map[]{xm};
static constexpr uint32_t mapSize{sizeof(map) / sizeof(uint32_t)};
static_assert(mapSize == 64);
if (excepts == 0 || excepts >= mapSize) {
terminator.Crash("Invalid excepts value: %d", excepts);
}
uint32_t except_value = map[excepts];
if (except_value == 0) {
terminator.Crash(
"Excepts value %d not supported by flang runtime", excepts);
}
return except_value;
}
// Verify that the size of ieee_modes_type and ieee_status_type objects from
// intrinsic module file __fortran_ieee_exceptions.f90 are large enough to
// hold fenv_t object.
// TODO: fenv_t can be way larger than
// sizeof(int) * _FORTRAN_RUNTIME_IEEE_FENV_T_EXTENT
// on some systems, e.g. Solaris, so omit object size comparison for now.
// TODO: consider femode_t object size comparison once its more mature.
// Check if the processor has the ability to control whether to halt or
// continue execution when a given exception is raised.
bool RTNAME(SupportHalting)([[maybe_unused]] uint32_t except) {
#if (defined(__arm__) || defined(__aarch64__)) && !defined(_WIN32)
except = RTNAME(MapException)(except);
int currentSet = fegetexcept(), flipSet, ok;
if (currentSet & except) {
ok = fedisableexcept(except);
flipSet = fegetexcept();
ok |= feenableexcept(except);
} else {
ok = feenableexcept(except);
flipSet = fegetexcept();
ok |= fedisableexcept(except);
}
return ok != -1 && currentSet != flipSet;
#else
return false;
#endif
}
bool RTNAME(GetUnderflowMode)(void) {
#if __x86_64__
// The MXCSR Flush to Zero flag is the negation of the ieee_get_underflow_mode
// GRADUAL argument. It affects real computations of kinds 3, 4, and 8.
return _MM_GET_FLUSH_ZERO_MODE() == _MM_FLUSH_ZERO_OFF;
#else
return false;
#endif
}
void RTNAME(SetUnderflowMode)(bool flag) {
#if __x86_64__
// The MXCSR Flush to Zero flag is the negation of the ieee_set_underflow_mode
// GRADUAL argument. It affects real computations of kinds 3, 4, and 8.
_MM_SET_FLUSH_ZERO_MODE(flag ? _MM_FLUSH_ZERO_OFF : _MM_FLUSH_ZERO_ON);
#endif
}
} // extern "C"
} // namespace Fortran::runtime