llvm/libc/src/__support/FPUtil/x86_64/FEnvImpl.h

//===-- x86_64 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_SRC___SUPPORT_FPUTIL_X86_64_FENVIMPL_H
#define LLVM_LIBC_SRC___SUPPORT_FPUTIL_X86_64_FENVIMPL_H

#include "src/__support/macros/attributes.h" // LIBC_INLINE
#include "src/__support/macros/config.h"
#include "src/__support/macros/properties/architectures.h"

#if !defined(LIBC_TARGET_ARCH_IS_X86)
#error "Invalid include"
#endif

#include <stdint.h>

#include "hdr/types/fenv_t.h"
#include "src/__support/macros/sanitizer.h"

namespace LIBC_NAMESPACE_DECL {
namespace fputil {

namespace internal {

// Normally, one should be able to define FE_* macros to the exact rounding mode
// encodings. However, since we want LLVM libc to be compiled against headers
// from other libcs, we cannot assume that FE_* macros are always defined in
// such a manner. So, we will define enums corresponding to the x86_64 bit
// encodings. The implementations can map from FE_* to the corresponding enum
// values.

// The rounding control values in the x87 control register and the MXCSR
// register have the same 2-bit enoding but have different bit positions.
// See below for the bit positions.
struct RoundingControlValue {};

static constexpr uint16_t X87_ROUNDING_CONTROL_BIT_POSITION =;
static constexpr uint16_t MXCSR_ROUNDING_CONTROL_BIT_POSITION =;

// The exception flags in the x87 status register and the MXCSR have the same
// encoding as well as the same bit positions.
struct ExceptionFlags {};

// The exception control bits occupy six bits, one bit for each exception.
// In the x87 control word, they occupy the first 6 bits. In the MXCSR
// register, they occupy bits 7 to 12.
static constexpr uint16_t X87_EXCEPTION_CONTROL_BIT_POSITION =;
static constexpr uint16_t X87_EXCEPTION_CONTROL_BIT_POSITION_HIGH =;
static constexpr uint16_t MXCSR_EXCEPTION_CONTOL_BIT_POISTION =;

// Exception flags are individual bits in the corresponding registers.
// So, we just OR the bit values to get the full set of exceptions.
LIBC_INLINE uint16_t get_status_value_for_except(int excepts) {}

LIBC_INLINE int exception_status_to_macro(uint16_t status) {}

struct X87StateDescriptor {};

LIBC_INLINE uint16_t get_x87_control_word() {}

LIBC_INLINE void write_x87_control_word(uint16_t w) {}

LIBC_INLINE uint16_t get_x87_status_word() {}

LIBC_INLINE void clear_x87_exceptions() {}

LIBC_INLINE uint32_t get_mxcsr() {}

LIBC_INLINE void write_mxcsr(uint32_t w) {}

LIBC_INLINE void get_x87_state_descriptor(X87StateDescriptor &s) {}

LIBC_INLINE void write_x87_state_descriptor(const X87StateDescriptor &s) {}

LIBC_INLINE void fwait() {}

} // namespace internal

LIBC_INLINE int enable_except(int excepts) {}

LIBC_INLINE int disable_except(int excepts) {}

LIBC_INLINE int get_except() {}

LIBC_INLINE int clear_except(int excepts) {}

LIBC_INLINE int test_except(int excepts) {}

// Sets the exception flags but does not trigger the exception handler.
LIBC_INLINE int set_except(int excepts) {}

LIBC_INLINE int raise_except(int excepts) {}

LIBC_INLINE int get_round() {}

LIBC_INLINE int set_round(int mode) {}

namespace internal {

#if defined(_WIN32)
// MSVC fenv.h defines a very simple representation of the floating point state
// which just consists of control and status words of the x87 unit.
struct FPState {
  uint32_t control_word;
  uint32_t status_word;
};
#elif defined(__APPLE__)
struct FPState {
  uint16_t control_word;
  uint16_t status_word;
  uint32_t mxcsr;
  uint8_t reserved[8];
};
#else
struct FPState {};
#endif // _WIN32

} // namespace internal

static_assert;

#ifdef _WIN32

// The exception flags in the Windows FEnv struct and the MXCSR have almost
// reversed bit positions.
struct WinExceptionFlags {
  static constexpr uint32_t INEXACT_WIN = 0x01;
  static constexpr uint32_t UNDERFLOW_WIN = 0x02;
  static constexpr uint32_t OVERFLOW_WIN = 0x04;
  static constexpr uint32_t DIV_BY_ZERO_WIN = 0x08;
  static constexpr uint32_t INVALID_WIN = 0x10;
  static constexpr uint32_t DENORMAL_WIN = 0x20;

  // The Windows FEnv struct has a second copy of all of these bits in the high
  // byte of the 32 bit control word. These are used as the source of truth when
  // calling fesetenv.
  static constexpr uint32_t HIGH_OFFSET = 24;

  static constexpr uint32_t HIGH_INEXACT = INEXACT_WIN << HIGH_OFFSET;
  static constexpr uint32_t HIGH_UNDERFLOW = UNDERFLOW_WIN << HIGH_OFFSET;
  static constexpr uint32_t HIGH_OVERFLOW = OVERFLOW_WIN << HIGH_OFFSET;
  static constexpr uint32_t HIGH_DIV_BY_ZERO = DIV_BY_ZERO_WIN << HIGH_OFFSET;
  static constexpr uint32_t HIGH_INVALID = INVALID_WIN << HIGH_OFFSET;
  static constexpr uint32_t HIGH_DENORMAL = DENORMAL_WIN << HIGH_OFFSET;
};

/*
    fenv_t control word format:

    Windows (at least for x64) uses a 4 byte control fenv control word stored in
    a 32 bit integer. The first byte contains just the rounding mode and the
    exception masks, while the last two bytes contain that same information as
    well as the flush-to-zero and denormals-are-zero flags. The flags are
    represented with a truth table:

    00 - No flags set
    01 - Flush-to-zero and Denormals-are-zero set
    11 - Flush-to-zero set
    10 - Denormals-are-zero set

    U represents unused.

     +-----Rounding Mode-----+
     |                       |
    ++                      ++
    ||                      ||
    RRMMMMMM UUUUUUUU UUUUFFRR UUMMMMMM
      |    |              ||     |    |
      +----+      flags---++     +----+
           |                          |
           +------Exception Masks-----+


    fenv_t status word format:

    The status word is a lot simpler for this conversion, since only the
    exception flags are used in the MXCSR.

      +----+---Exception Flags---+----+
      |    |                     |    |
    UUEEEEEE UUUUUUUU UUUUUUUU UUEEEEEE



    MXCSR Format:

    The MXCSR format is the same information, just organized differently. Since
    the fenv_t struct for windows doesn't include the mxcsr bits, they must be
    generated from the control word bits.

      Exception Masks---+           +---Exception Flags
                        |           |
     Flush-to-zero---+  +----+ +----+
                     |  |    | |    |
                     FRRMMMMMMDEEEEEE
                      ||      |
                      ++      +---Denormals-are-zero
                      |
                      +---Rounding Mode


    The mask and flag order is as follows:

    fenv_t      mxcsr

    denormal    inexact
    invalid     underflow
    div by 0    overflow
    overflow    div by 0
    underflow   denormal
    inexact     invalid

    This is almost reverse, except for denormal and invalid which are in the
    same order in both.
  */

LIBC_INLINE int get_env(fenv_t *envp) {
  internal::FPState *state = reinterpret_cast<internal::FPState *>(envp);

  uint32_t status_word = 0;
  uint32_t control_word = 0;

  uint32_t mxcsr = internal::get_mxcsr();

  // Set exception flags in the status word
  status_word |= (mxcsr & (internal::ExceptionFlags::INVALID_F |
                           internal::ExceptionFlags::DENORMAL_F))
                 << 4;
  status_word |= (mxcsr & internal::ExceptionFlags::DIV_BY_ZERO_F) << 1;
  status_word |= (mxcsr & internal::ExceptionFlags::OVERFLOW_F) >> 1;
  status_word |= (mxcsr & internal::ExceptionFlags::UNDERFLOW_F) >> 3;
  status_word |= (mxcsr & internal::ExceptionFlags::INEXACT_F) >> 5;
  status_word |= status_word << WinExceptionFlags::HIGH_OFFSET;

  // Set exception masks in bits 0-5 and 24-29
  control_word |= (mxcsr & ((internal::ExceptionFlags::INVALID_F |
                             internal::ExceptionFlags::DENORMAL_F)
                            << 7)) >>
                  3;
  control_word |= (mxcsr & (internal::ExceptionFlags::DIV_BY_ZERO_F << 7)) >> 6;
  control_word |= (mxcsr & (internal::ExceptionFlags::OVERFLOW_F << 7)) >> 8;
  control_word |= (mxcsr & (internal::ExceptionFlags::UNDERFLOW_F << 7)) >> 10;
  control_word |= (mxcsr & (internal::ExceptionFlags::INEXACT_F << 7)) >> 12;
  control_word |= control_word << WinExceptionFlags::HIGH_OFFSET;

  // Set rounding in bits 8-9 and 30-31
  control_word |= (mxcsr & 0x6000) >> 5;
  control_word |= (mxcsr & 0x6000) << 17;

  // Set flush-to-zero in bit 10
  control_word |= (mxcsr & 0x8000) >> 5;

  // Set denormals-are-zero xor flush-to-zero in bit 11
  control_word |= (((mxcsr & 0x8000) >> 9) ^ (mxcsr & 0x0040)) << 5;

  state->control_word = control_word;
  state->status_word = status_word;
  return 0;
}

LIBC_INLINE int set_env(const fenv_t *envp) {
  const internal::FPState *state =
      reinterpret_cast<const internal::FPState *>(envp);

  uint32_t mxcsr = 0;

  // Set exception flags from the status word
  mxcsr |= static_cast<uint16_t>(
      (state->status_word &
       (WinExceptionFlags::HIGH_DENORMAL | WinExceptionFlags::HIGH_INVALID)) >>
      28);
  mxcsr |= static_cast<uint16_t>(
      (state->status_word & WinExceptionFlags::HIGH_DIV_BY_ZERO) >> 25);
  mxcsr |= static_cast<uint16_t>(
      (state->status_word & WinExceptionFlags::HIGH_OVERFLOW) >> 23);
  mxcsr |= static_cast<uint16_t>(
      (state->status_word & WinExceptionFlags::HIGH_UNDERFLOW) >> 21);
  mxcsr |= static_cast<uint16_t>(
      (state->status_word & WinExceptionFlags::HIGH_INEXACT) >> 19);

  // Set denormals-are-zero from bit 10 xor bit 11
  mxcsr |= static_cast<uint16_t>(
      (((state->control_word & 0x800) >> 1) ^ (state->control_word & 0x400)) >>
      4);

  // Set exception masks from bits 24-29
  mxcsr |= static_cast<uint16_t>(
      (state->control_word &
       (WinExceptionFlags::HIGH_DENORMAL | WinExceptionFlags::HIGH_INVALID)) >>
      21);
  mxcsr |= static_cast<uint16_t>(
      (state->control_word & WinExceptionFlags::HIGH_DIV_BY_ZERO) >> 18);
  mxcsr |= static_cast<uint16_t>(
      (state->control_word & WinExceptionFlags::HIGH_OVERFLOW) >> 16);
  mxcsr |= static_cast<uint16_t>(
      (state->control_word & WinExceptionFlags::HIGH_UNDERFLOW) >> 14);
  mxcsr |= static_cast<uint16_t>(
      (state->control_word & WinExceptionFlags::HIGH_INEXACT) >> 12);

  // Set rounding from bits 30-31
  mxcsr |= static_cast<uint16_t>((state->control_word & 0xc0000000) >> 17);

  // Set flush-to-zero from bit 10
  mxcsr |= static_cast<uint16_t>((state->control_word & 0x400) << 5);

  internal::write_mxcsr(mxcsr);
  return 0;
}
#else
LIBC_INLINE int get_env(fenv_t *envp) {}

LIBC_INLINE int set_env(const fenv_t *envp) {}
#endif

} // namespace fputil
} // namespace LIBC_NAMESPACE_DECL

#endif // LLVM_LIBC_SRC___SUPPORT_FPUTIL_X86_64_FENVIMPL_H