diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt --- a/libc/config/linux/aarch64/entrypoints.txt +++ b/libc/config/linux/aarch64/entrypoints.txt @@ -20,6 +20,13 @@ # errno.h entrypoints libc.src.errno.__errno_location + # fenv.h entrypoints + libc.src.fenv.feclearexcept + libc.src.fenv.fegetround + libc.src.fenv.fesetround + libc.src.fenv.feraiseexcept + libc.src.fenv.fetestexcept + # stdlib.h entrypoints libc.src.stdlib.abs libc.src.stdlib.labs diff --git a/libc/utils/FPUtil/FEnv.h b/libc/utils/FPUtil/FEnv.h --- a/libc/utils/FPUtil/FEnv.h +++ b/libc/utils/FPUtil/FEnv.h @@ -11,6 +11,8 @@ #ifdef __x86_64__ #include "x86_64/FEnv.h" +#elif defined(__aarch64__) +#include "aarch64/FEnv.h" #else #include "DummyFEnv.h" #endif diff --git a/libc/utils/FPUtil/aarch64/FEnv.h b/libc/utils/FPUtil/aarch64/FEnv.h new file mode 100644 --- /dev/null +++ b/libc/utils/FPUtil/aarch64/FEnv.h @@ -0,0 +1,204 @@ +//===-- aarch64 floating point env manipulation functions -------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIBC_UTILS_FPUTIL_AARCH64_FENV_H +#define LLVM_LIBC_UTILS_FPUTIL_AARCH64_FENV_H + +#include +#include +#include + +#include "utils/FPUtil/FPBits.h" + +namespace __llvm_libc { +namespace fputil { + +struct FEnv { + static constexpr uint32_t ToNearest = 0x0; + static constexpr uint32_t Upward = 0x1; + static constexpr uint32_t Downward = 0x2; + static constexpr uint32_t TowardZero = 0x3; + + static constexpr uint32_t Invalid = 0x1; + static constexpr uint32_t DivByZero = 0x2; + static constexpr uint32_t Overflow = 0x4; + static constexpr uint32_t Underflow = 0x8; + static constexpr uint32_t Inexact = 0x10; + + // Zero-th bit is the first bit. + static constexpr uint32_t RoundingControlBitPosition = 22; + static constexpr uint32_t ExceptionStatusFlagsBitPosition = 0; + static constexpr uint32_t ExceptionControlFlagsBitPosition = 8; + + static inline uint32_t getStatusValueForExcept(int excepts) { + return (excepts & FE_INVALID ? Invalid : 0) | + (excepts & FE_DIVBYZERO ? DivByZero : 0) | + (excepts & FE_OVERFLOW ? Overflow : 0) | + (excepts & FE_UNDERFLOW ? Underflow : 0) | + (excepts & FE_INEXACT ? Inexact : 0); + } + + static inline int exceptionStatusToMacro(uint32_t status) { + return (status & Invalid ? FE_INVALID : 0) | + (status & DivByZero ? FE_DIVBYZERO : 0) | + (status & Overflow ? FE_OVERFLOW : 0) | + (status & Underflow ? FE_UNDERFLOW : 0) | + (status & Inexact ? FE_INEXACT : 0); + } + + static uint32_t getControlWord() { return __arm_rsr("fpcr"); } + + static void writeControlWord(uint32_t fpcr) { __arm_wsr("fpcr", fpcr); } + + static uint32_t getStatusWord() { return __arm_rsr("fpsr"); } + + static void writeStatusWord(uint32_t fpsr) { __arm_wsr("fpsr", fpsr); } +}; + +static inline int enableExcept(int excepts) { + uint32_t newExcepts = FEnv::getStatusValueForExcept(excepts); + uint32_t controlWord = FEnv::getControlWord(); + int oldExcepts = + (controlWord >> FEnv::ExceptionControlFlagsBitPosition) & 0x1F; + controlWord |= (newExcepts << FEnv::ExceptionControlFlagsBitPosition); + FEnv::writeControlWord(controlWord); + return FEnv::exceptionStatusToMacro(oldExcepts); +} + +static inline int disableExcept(int excepts) { + uint32_t disabledExcepts = FEnv::getStatusValueForExcept(excepts); + uint32_t controlWord = FEnv::getControlWord(); + int oldExcepts = + (controlWord >> FEnv::ExceptionControlFlagsBitPosition) & 0x1F; + controlWord &= ~(disabledExcepts << FEnv::ExceptionControlFlagsBitPosition); + FEnv::writeControlWord(controlWord); + return FEnv::exceptionStatusToMacro(oldExcepts); +} + +static inline int clearExcept(int excepts) { + uint32_t controlWord = FEnv::getControlWord(); + uint32_t toClear = FEnv::getStatusValueForExcept(excepts); + controlWord &= ~(toClear << FEnv::ExceptionStatusFlagsBitPosition); + FEnv::writeStatusWord(controlWord); + return 0; +} + +static inline int testExcept(int excepts) { + uint32_t toTest = FEnv::getStatusValueForExcept(excepts); + uint32_t statusWord = FEnv::getStatusWord(); + return FEnv::exceptionStatusToMacro( + (statusWord >> FEnv::ExceptionStatusFlagsBitPosition) & toTest); +} + +static inline int raiseExcept(int excepts) { + float zero = 0.0f; + float one = 1.0f; + float largeValue = FPBits(FPBits::maxNormal); + float smallValue = FPBits(FPBits::minNormal); + auto divfunc = [](float a, float b) { + __asm__ __volatile__("ldr s0, %0\n\t" + "ldr s1, %1\n\t" + "fdiv s0, s0, s1\n\t" + : // No outputs + : "m"(a), "m"(b) + : "s0", "s1" /* s0 and s1 are clobbered */); + }; + + uint32_t toRaise = FEnv::getStatusValueForExcept(excepts); + + if (toRaise & FEnv::Invalid) { + divfunc(zero, zero); + uint32_t statusWord = FEnv::getStatusWord(); + if (!((statusWord >> FEnv::ExceptionStatusFlagsBitPosition) & + FEnv::Invalid)) + return -1; + } + + if (toRaise & FEnv::DivByZero) { + divfunc(one, zero); + uint32_t statusWord = FEnv::getStatusWord(); + if (!((statusWord >> FEnv::ExceptionStatusFlagsBitPosition) & + FEnv::DivByZero)) + return -1; + } + if (toRaise & FEnv::Overflow) { + divfunc(largeValue, smallValue); + uint32_t statusWord = FEnv::getStatusWord(); + if (!((statusWord >> FEnv::ExceptionStatusFlagsBitPosition) & + FEnv::Overflow)) + return -1; + } + if (toRaise & FEnv::Underflow) { + divfunc(smallValue, largeValue); + uint32_t statusWord = FEnv::getStatusWord(); + if (!((statusWord >> FEnv::ExceptionStatusFlagsBitPosition) & + FEnv::Underflow)) + return -1; + } + if (toRaise & FEnv::Inexact) { + float two = 2.0f; + float three = 3.0f; + // 2.0 / 3.0 cannot be represented exactly in any radix 2 floating point + // format. + divfunc(two, three); + uint32_t statusWord = FEnv::getStatusWord(); + if (!((statusWord >> FEnv::ExceptionStatusFlagsBitPosition) & + FEnv::Inexact)) + return -1; + } + return 0; +} + +static inline int getRound() { + uint32_t roundingMode = + (FEnv::getControlWord() >> FEnv::RoundingControlBitPosition) & 0x3; + switch (roundingMode) { + case FEnv::ToNearest: + return FE_TONEAREST; + case FEnv::Downward: + return FE_DOWNWARD; + case FEnv::Upward: + return FE_UPWARD; + case FEnv::TowardZero: + return FE_TOWARDZERO; + default: + return -1; // Error value. + } +} + +static inline int setRound(int mode) { + uint16_t bitValue; + switch (mode) { + case FE_TONEAREST: + bitValue = FEnv::ToNearest; + break; + case FE_DOWNWARD: + bitValue = FEnv::Downward; + break; + case FE_UPWARD: + bitValue = FEnv::Upward; + break; + case FE_TOWARDZERO: + bitValue = FEnv::TowardZero; + break; + default: + return 1; // To indicate failure + } + + uint32_t controlWord = FEnv::getControlWord(); + controlWord &= ~(0x3 << FEnv::RoundingControlBitPosition); + controlWord |= (bitValue << FEnv::RoundingControlBitPosition); + FEnv::writeControlWord(controlWord); + + return 0; +} + +} // namespace fputil +} // namespace __llvm_libc + +#endif // LLVM_LIBC_UTILS_FPUTIL_AARCH64_FENV_H