From 96fbf861819e9f559eea75bdf329a6fd6769f5db Mon Sep 17 00:00:00 2001 From: vector-of-bool Date: Thu, 9 Feb 2023 03:20:04 +0000 Subject: [PATCH 01/12] Configure/import-time logic support for libdfp --- CMakeLists.txt | 12 ++-- cmake/ImportDFP.cmake | 107 +++++++++++++++++++++++----------- cmake/IntelDFP.cmake | 17 ++---- cmake/mongocrypt-config.cmake | 39 ++++++++++--- 4 files changed, 113 insertions(+), 62 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 55a7d3044..773bb31b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -282,7 +282,7 @@ target_link_libraries ( kms_message_static $ PUBLIC - mongocrypt::intel_dfp + mongocrypt::dfp ${CMAKE_DL_LIBS} ) @@ -340,7 +340,7 @@ target_link_libraries ( kms_message_static $ PUBLIC - mongocrypt::intel_dfp + mongocrypt::dfp ${CMAKE_THREAD_LIBS_INIT} ${CMAKE_DL_LIBS} ) @@ -360,7 +360,7 @@ if (ENABLE_BUILD_FOR_PPA) set (PKG_CONFIG_STATIC_LIBS "${PKG_CONFIG_STATIC_LIBS} -pthread") endif () if (MONGOCRYPT_DFP_DIR STREQUAL "USE-SYSTEM") - get_property (SYSTEM_DFP_LOC TARGET intel_dfp PROPERTY IMPORTED_LOCATION) + get_property (SYSTEM_DFP_LOC TARGET _mongocrypt::system-dfp PROPERTY IMPORTED_LOCATION) set (PKG_CONFIG_STATIC_LIBS "${PKG_CONFIG_STATIC_LIBS} ${SYSTEM_DFP_LOC}") endif () @@ -541,7 +541,7 @@ if ("cxx_relaxed_constexpr" IN_LIST CMAKE_CXX_COMPILE_FEATURES) target_link_libraries ("${exe_name}" PRIVATE mongocrypt mongo::mlib - mongocrypt::intel_dfp + mongocrypt::dfp Threads::Threads _mongocrypt::libbson_for_static ) @@ -636,10 +636,6 @@ if (ENABLE_BUILD_FOR_PPA) endif () set (PKG_CONFIG_LIBS "${PKG_CONFIG_LIBS} -pthread") endif () -if (MONGOCRYPT_DFP_DIR STREQUAL "USE-SYSTEM") - get_property (SYSTEM_DFP_LOC TARGET intel_dfp PROPERTY IMPORTED_LOCATION) - set (PKG_CONFIG_LIBS "${PKG_CONFIG_LIBS} ${SYSTEM_DFP_LOC}") -endif () set (PKG_CONFIG_CFLAGS "-I\${includedir}") set (PKG_CONFIG_STATIC_CFLAGS "${PKG_CONFIG_CFLAGS} -DMONGOCRYPT_STATIC_DEFINE -DKMS_MSG_STATIC") configure_file ( diff --git a/cmake/ImportDFP.cmake b/cmake/ImportDFP.cmake index a5606bffb..685490fbe 100644 --- a/cmake/ImportDFP.cmake +++ b/cmake/ImportDFP.cmake @@ -2,24 +2,21 @@ This file handles importing the DFP (decimal floating point) library for decimal128 support. It is patterned after ImportBSON in this same directory. - Initially, the only supported DFP implementation is Intel DFP. However, this module will allow - for the future addition of support for libdfp. - - This file defines, exports, and installs one INTERFACE target: mongocrypt::intel_dfp. + This file defines, exports, and installs one INTERFACE target: _mongocrypt::dfp. The target(s) from this file are used to link the DFP library correctly for the build configuration of libmongocrypt. At find_package() time, we can resolve these interface targets to link to the DFP library based on the build configurations of libmongocrypt. In the initial implementation both mongo::mongocrypt and mongo::mongocrypt_static must link to - mongocrypt::intel_dfp (this is because if we link to the Intel DFP which is vendored with + _mongocrypt::dfp (this is because if we link to the Intel DFP which is vendored with libmongocrypt then we will link the object files directly and if we use the system Intel DFP then we will be linking with .a static library archives). The default behavior is to use the Intel DFP which is vendored in this repository. By setting - MONGOCRYPT_DFP_DIR=USE-SYSTEM the build will assume that an appropriate Intel DFP implementation + MONGOCRYPT_DFP_DIR=USE-SYSTEM the build will assume that an appropriate DFP implementation can be found in a location where it has been installed system-wide (most likely under /usr or - /usr/local). + /usr/local). It will search for an installation of IntelDFP or of libdfp. ]] if (DEFINED MONGOCRYPT_DFP_DIR AND NOT MONGOCRYPT_DFP_DIR STREQUAL "USE-SYSTEM") @@ -27,42 +24,82 @@ if (DEFINED MONGOCRYPT_DFP_DIR AND NOT MONGOCRYPT_DFP_DIR STREQUAL "USE-SYSTEM") endif () function (_import_dfp) - find_library (_MONGOCRYPT_SYSTEM_INTEL_DFP_STATIC "${CMAKE_STATIC_LIBRARY_PREFIX}bidgcc000${CMAKE_STATIC_LIBRARY_SUFFIX}") - find_path (_MONGOCRYPT_SYSTEM_INTEL_DFP_INCLUDE_DIR bid_conf.h) - add_library (intel_dfp STATIC IMPORTED) - set_target_properties (intel_dfp PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${_MONGOCRYPT_SYSTEM_INTEL_DFP_INCLUDE_DIR}" - ) - set_property (TARGET intel_dfp PROPERTY IMPORTED_LOCATION "${_MONGOCRYPT_SYSTEM_INTEL_DFP_STATIC}") - set_property ( - CACHE _MONGOCRYPT_SYSTEM_INTEL_DFP_INCLUDE_DIR - PROPERTY ADVANCED - TRUE + if (NOT _MONGOCRYPT_SYSTEM_DFP_LIB OR NOT _MONGOCRYPT_SYSTEM_DFP_INCLUDE_DIR) + message (CHECK_START "Searching for a decimal floating-point library") + # Clear prior settings if either one may have been unset: + unset (_MONGOCRYPT_SYSTEM_DFP_LIB CACHE) + unset (_MONGOCRYPT_SYSTEM_DFP_INCLUDE_DIR CACHE) + # Search for the IntelDFP library: + find_library ( + _inteldfp_lib + NAMES bidgcc000 "${CMAKE_STATIC_LIBRARY_PREFIX}bidgcc000${CMAKE_STATIC_LIBRARY_SUFFIX}" + NO_CACHE + ) + # Search for the libdfp library: + find_library ( + _libdfp_lib + NAMES dfp "${CMAKE_STATIC_LIBRARY_PREFIX}dfp${CMAKE_STATIC_LIBRARY_SUFFIX}" + NO_CACHE + ) + + # Search for the include-path for IntelDFP: + find_path (_inteldfp_bid_conf_h_dir bid_conf.h NO_CACHE) + # Search for math.h contained in libdfp: + find_file (_libdfp_dfp_math_h "dfp/math.h" NO_CACHE) + + set (_lib NOTFOUND) + set (_inc_dir NOTFOUND) + if (_inteldfp_lib AND _inteldfp_bid_conf_h_dir) + # Use libdfp: + message (CHECK_PASS "Using IntelDFP: ${_inteldfp_lib}") + set (_lib "${_inteldfp_lib}") + set (_inc_dir "${_inteldfp_bid_conf_h_dir}") + elseif (_libdfp_lib AND _libdfp_dfp_math_h) + # Use libdfp: + message (CHECK_PASS "Using libdfp: ${_libdfp_lib}") + set (_lib "${_libdfp_lib}") + # We want to add the 'dfp/' directory as an include-dir, so it intercepts + # the default stdlib headers: + get_filename_component (_inc_dir "${_libdfp_dfp_math_h}" DIRECTORY) + else () + # Nope: + message (CHECK_FAIL "No decimal floating-point library was found.") + message (SEND_ERROR "Failed to import a decimal floating-point library from the system") + endif () + + set (_MONGOCRYPT_SYSTEM_DFP_LIB "${_lib}" CACHE PATH "System DFP library to use") + set (_MONGOCRYPT_SYSTEM_DFP_INCLUDE_DIR "${_inc_dir}" CACHE PATH "include-search-dir for the system DFP library") + mark_as_advanced (_MONGOCRYPT_SYSTEM_DFP_LIB _MONGOCRYPT_SYSTEM_DFP_INCLUDE_DIR) + endif () + + add_library (_mongocrypt::system-dfp UNKNOWN IMPORTED) + set_target_properties ( + _mongocrypt::system-dfp PROPERTIES + IMPORTED_LOCATION "${_MONGOCRYPT_SYSTEM_DFP_LIB}" + INTERFACE_INCLUDE_DIRECTORIES "${_MONGOCRYPT_SYSTEM_DFP_INCLUDE_DIR}" ) endfunction () +# This library is used to pivot the used DFP library at configure/find_package time: +add_library (_mongocrypt-dfp INTERFACE) +add_library (mongocrypt::dfp ALIAS _mongocrypt-dfp) +install (TARGETS _mongocrypt-dfp EXPORT mongocrypt_targets) + if (NOT DEFINED MONGOCRYPT_DFP_DIR) # The user did not provide a MONGOCRYPT_DFP_DIR, so we'll set one up include (IntelDFP) elseif (MONGOCRYPT_DFP_DIR STREQUAL "USE-SYSTEM") - message (STATUS "NOTE: Using system-wide Intel DFP library. This is intended only for package maintainers.") - set (USE_SYSTEM_INTEL_DFP "ON") + message (STATUS "NOTE: Using system-wide DFP library. This is intended only for package maintainers.") + # Do the import in a function to isolate variable scope _import_dfp () - # Define interface targets to be used to control the DFP used at both build and import time. - # Refer to mongocrypt-config.cmake to see how these targets are used by consumers - add_library (_mongocrypt-intel_dfp INTERFACE) - add_library (mongocrypt::intel_dfp ALIAS _mongocrypt-intel_dfp) - install ( - TARGETS _mongocrypt-intel_dfp - EXPORT mongocrypt_targets - ) - - # Link to Intel DFP, only exporting that usage for the local build tree. - # The mongocrypt-config file will later add the appropriate link library for downstream - # users during find_package() - target_link_libraries (_mongocrypt-intel_dfp INTERFACE $) - + # Link to the import target for the system's DFP library. + # mongocrypt-config.cmake will later add a matching IMPORTED target + # for downstream users during find_package() + target_link_libraries (_mongocrypt-dfp INTERFACE _mongocrypt::system-dfp) + # Hints for mongocrypt-config.cmake: + set (USE_SYSTEM_DFP "ON") + get_target_property (_loc _mongocrypt::system-dfp IMPORTED_LOCATION) + get_filename_component (SYSTEM_DFP_FILENAME "${_loc}" NAME) endif () - diff --git a/cmake/IntelDFP.cmake b/cmake/IntelDFP.cmake index fb7714862..9ed784aa5 100644 --- a/cmake/IntelDFP.cmake +++ b/cmake/IntelDFP.cmake @@ -376,30 +376,23 @@ target_compile_definitions (intel_dfp_obj PUBLIC target_compile_options (intel_dfp_obj PRIVATE -w) target_include_directories(intel_dfp_obj PUBLIC ${intel_dfp_SOURCE_DIR}/LIBRARY/src) -# Define an interface library that attaches the built TUs to the consumer -add_library (_mongocrypt_intel_dfp INTERFACE) -add_library (mongocrypt::intel_dfp ALIAS _mongocrypt_intel_dfp) - -target_sources (_mongocrypt_intel_dfp +target_sources (_mongocrypt-dfp #[[ - For targets *within this build* that link with mongocrypt::intel_dfp, + For targets *within this build* that link with mongocrypt::dfp, inject the generated TUs (object files) from the intel_dfp_obj library. This will be stripped out of the interface library when it is installed, - since we don't want to ship the DFP object separately. Instead, users + since we don't want to ship the DFP objects separately. Instead, users will link to libmongocrypt, which will contain the necessary TUs for the library (because they link to this interface library). ]] INTERFACE $> ) -target_link_libraries (_mongocrypt_intel_dfp +target_link_libraries (_mongocrypt-dfp INTERFACE + # IntelDFP requires that we set certain definitions just to consume its headers: $ # We do want to propagate an interface requirement: Some platforms need a # separate link library to support special math functions. $<$:m> ) - -# Give the installed target a name to indicate its hidden-ness -set_property (TARGET _mongocrypt_intel_dfp PROPERTY EXPORT_NAME private::intel_dfp_interface) -install (TARGETS _mongocrypt_intel_dfp EXPORT mongocrypt_targets) diff --git a/cmake/mongocrypt-config.cmake b/cmake/mongocrypt-config.cmake index db82ca2f5..2ad85df76 100644 --- a/cmake/mongocrypt-config.cmake +++ b/cmake/mongocrypt-config.cmake @@ -31,12 +31,37 @@ if (_using_shared_libbson AND DEFINED MONGOCRYPT_LIBBSON_SHARED_USE) ) endif () -set (_using_system_intel_dfp "@USE_SYSTEM_INTEL_DFP@") +#[[ BOOL: + Whether the packaged libmongocrypt expects to use the system's DFP library. Like with + libbson noted above, the default configuration embeds the TUs for IntelDFP within libmongocrypt + itself. These TUs are defined with special names and won't collide with other DFP libraries + that might be used by a final application. -if (_using_system_intel_dfp) - find_library (_MONGOCRYPT_SYSTEM_INTEL_DFP_STATIC "${CMAKE_STATIC_LIBRARY_PREFIX}bidgcc000${CMAKE_STATIC_LIBRARY_SUFFIX}") - set_property ( - TARGET mongo::_mongocrypt-intel_dfp - PROPERTY IMPORTED_LOCATION "${_MONGOCRYPT_SYSTEM_INTEL_DFP_STATIC}" - ) + If we were built against a system DFP, libmongocrypt will have a transitive usage of + _mongocrypt::system-dfp. We will need to define this IMPORTED target. +]] +set (_using_system_dfp "@USE_SYSTEM_DFP@") +#[[ Filename: + If this libmongocrypt was built with the system's DFP, this is the filename of the + library that was used during the find build. This gives us sufficient information to know + resolve the appropriate library for transitive users. +]] +set (_system_dfp_filename "@SYSTEM_DFP_FILENAME@") + +if (_using_system_dfp) + # Find the library on the system that matches the name of the library that we used when + # building libmongocrypt. If we found a 'libdfp.so' library, we also want to check for 'libdfp.so.1', + # since that is the preferred binary name. + find_library (MONGOCRYPT_SYSTEM_DFP_LIB NAMES "${_system_dfp_filename}" "${_system_dfp_filename}.1") + if (NOT MONGOCRYPT_SYSTEM_DFP_LIB) + message (WARNING + "Unable to find the '${_system_dfp_filename}' decimal floating-point library that is expected by libmongocrypt. " + "Application linking will likely fail. Set MONGOCRYPT_SYSTEM_DFP_LIB to a library path to use.") + else () + add_library (_mongocrypt::system-dfp UNKNOWN IMPORTED) + set_property ( + TARGET _mongocrypt::system-dfp + PROPERTY IMPORTED_LOCATION "${MONGOCRYPT_SYSTEM_DFP_LIB}" + ) + endif () endif () From 439d92a9f2d2e65300ee4a54c6e48b9f195d41fb Mon Sep 17 00:00:00 2001 From: vector-of-bool Date: Thu, 9 Feb 2023 03:42:43 +0000 Subject: [PATCH 02/12] Support libdfp as a Decimal128 impl --- CMakeLists.txt | 1 + src/mc-dec128.c | 147 +++++++++++ src/mc-dec128.h | 529 +++++++++++++++++++++++----------------- src/mc-dec128.test.cpp | 19 +- src/mc-range-encoding.c | 13 +- 5 files changed, 481 insertions(+), 228 deletions(-) create mode 100644 src/mc-dec128.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 773bb31b9..e67e1cc56 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,6 +90,7 @@ set (MONGOCRYPT_SOURCES src/crypto/libcrypto.c src/crypto/none.c src/mc-array.c + src/mc-dec128.c src/mc-efc.c src/mc-fle2-find-range-payload.c src/mc-fle2-insert-update-payload.c diff --git a/src/mc-dec128.c b/src/mc-dec128.c new file mode 100644 index 000000000..ea0d236d5 --- /dev/null +++ b/src/mc-dec128.c @@ -0,0 +1,147 @@ +#include "./mc-dec128.h" + +mc_dec128_string +mc_dec128_to_string_ex (mc_dec128 d, mc_dec128_flagset *flags) +{ + mc_dec128_string out = {{0}}; +#if MC_HAVE_LIBDFP + MC_HOLD_FENV (-1, flags) + { + int ignore = strfromd128 (out.str, sizeof out.str, "%A", _mc_to_dfp (d)); + (void) ignore; + out.str[(sizeof out.str) - 1] = 0; + } +#else + mc_dec128_flagset zero_flags = {0}; + bid128_to_string ( + out.str, _mc_to_dfp (d), flags ? &flags->bits : &zero_flags.bits); +#endif + return out; +} + +mc_dec128 +mc_dec128_from_string_ex (const char *s, + mc_dec128_rounding_mode rnd, + mc_dec128_flagset *flags) +{ +#if MC_HAVE_LIBDFP + mc_dec128 ret; + char *fin = NULL; + MC_HOLD_FENV (rnd, flags) + { + ret = _dfp_to_mc (strtod128 (s, &fin)); + } + return ret; +#else + mc_dec128_flagset zero_flags = {0}; + return _dfp_to_mc (bid128_from_string ( + (char *) s, rnd, flags ? &flags->bits : &zero_flags.bits)); +#endif +} + + +char * +mc_dec128_to_new_decimal_string (mc_dec128 d) +{ + if (mc_dec128_is_zero (d)) { + // Just return "0" + char *s = (char *) calloc (2, 1); + if (s) { + s[0] = '0'; + } + return s; + } + + if (mc_dec128_is_negative (d)) { + // Negate the result, return a string with a '-' prefix + d = mc_dec128_negate (d); + char *s = mc_dec128_to_new_decimal_string (d); + if (!s) { + return NULL; + } + char *s1 = (char *) calloc (strlen (s) + 2, 1); + if (s1) { + s1[0] = '-'; + strcpy (s1 + 1, s); + } + free (s); + return s1; + } + + if (mc_dec128_isinf (d) || mc_dec128_isnan (d)) { + const char *r = mc_dec128_isinf (d) ? "Infinity" : "NaN"; + char *c = (char *) calloc (strlen (r) + 1, 1); + if (c) { + strcpy (c, r); + } + return c; + } + + const char DIGITS[] = "0123456789"; + const mc_dec128 TEN = MC_DEC128_C (10); + + // Format the whole and fractional part separately. + mc_dec128_modf_result modf = mc_dec128_modf (d); + + if (mc_dec128_is_zero (modf.frac)) { + // This is a non-zero integer + // Allocate enough digits: + mc_dec128 log10 = mc_dec128_round_integral_ex ( + mc_dec128_log10 (d), MC_DEC128_ROUND_UPWARD, NULL); + int64_t ndigits = mc_dec128_to_int64 (log10); + // +1 for null + char *strbuf = (char *) calloc ((size_t) (ndigits + 1), 1); + if (strbuf) { + // Write the string backwards: + char *optr = strbuf + ndigits - 1; + while (!mc_dec128_is_zero (modf.whole)) { + mc_dec128 rem = mc_dec128_fmod (modf.whole, TEN); + int64_t remi = mc_dec128_to_int64 (rem); + *optr-- = DIGITS[remi]; + // Divide ten + modf = mc_dec128_modf (mc_dec128_div (modf.whole, TEN)); + } + } + return strbuf; + } else if (mc_dec128_is_zero (modf.whole)) { + // This is only a fraction (less than one, but more than zero) + while (!mc_dec128_is_zero (mc_dec128_modf (d).frac)) { + d = mc_dec128_mul (d, TEN); + } + // 'd' is now a whole number + char *part = mc_dec128_to_new_decimal_string (d); + if (!part) { + return NULL; + } + char *buf = (char *) calloc (strlen (part) + 3, 1); + if (buf) { + buf[0] = '0'; + buf[1] = '.'; + strcpy (buf + 2, part); + } + free (part); + return buf; + } else { + // We have both a whole part and a fractional part + char *whole = mc_dec128_to_new_decimal_string (modf.whole); + if (!whole) { + return NULL; + } + char *frac = mc_dec128_to_new_decimal_string (modf.frac); + if (!frac) { + free (whole); + return NULL; + } + char *ret = (char *) calloc (strlen (whole) + strlen (frac) + 1, 1); + if (ret) { + char *out = ret; + strcpy (out, whole); + out += strlen (whole); + // "frac" contains a leading zero, which we don't want + strcpy (out, frac + 1); + } + free (whole); + free (frac); + return ret; + } +} diff --git a/src/mc-dec128.h b/src/mc-dec128.h index 4e8423311..4fc2d7738 100644 --- a/src/mc-dec128.h +++ b/src/mc-dec128.h @@ -1,16 +1,41 @@ #ifndef MC_DEC128_H_INCLUDED #define MC_DEC128_H_INCLUDED -#include +// Required macros to enable certain libdfp features +#ifndef __STDC_WANT_DEC_FP__ +#define __STDC_WANT_DEC_FP__ 1 +#endif +#ifndef __STDC_WANT_IEC_60559_DFP_EXT__ +#define __STDC_WANT_IEC_60559_DFP_EXT__ 1 +#endif -#include -#include -#include +// If libdfp is available, these will be intercepted by libdfp: +#include +#include +#include + +#ifdef _DFP_MATH_H +// We included libdfp's math.h +#define MC_HAVE_LIBDFP 1 +#define MC_IF_LIBDFP(...) __VA_ARGS__ +#define MC_IF_IntelDFP(...) +#else +// No libdfp. Search for IntelDFP: +#define MC_HAVE_LIBDFP 0 +#define MC_IF_LIBDFP(...) +#define MC_IF_IntelDFP(...) __VA_ARGS__ // Include the header that declares the DFP functions, which may be macros that // expand to renamed symbols: #include #include +#endif + +#include + +#include +#include +#include #include #include @@ -30,7 +55,8 @@ typedef enum mc_dec128_rounding_mode { } mc_dec128_rounding_mode; typedef struct mc_dec128_flagset { - _IDEC_flags bits; + MC_IF_IntelDFP (_IDEC_flags bits); + MC_IF_LIBDFP (int bits); } mc_dec128_flagset; // This alignment conditional is the same conditions used in Intel's DFP @@ -49,9 +75,13 @@ typedef struct mc_dec128_flagset { #endif #endif +MC_IF_LIBDFP (typedef _Decimal128 _mc_dec128_underlying); +MC_IF_IntelDFP (typedef BID_UINT128 _mc_dec128_underlying); + typedef union _mcDec128Align (16) { uint64_t _words[2]; + _mc_dec128_underlying _underlying; #if !defined(__INTELLISENSE__) && defined(__GNUC__) && defined(__amd64) && \ !defined(__APPLE__) && !defined(__clang__) // If supported by the compiler, emit a field that can be used to visualize @@ -125,24 +155,113 @@ static const mc_dec128 MC_DEC128_MINUSONE = MC_DEC128_C (-1); #define MC_DEC128_NEGATIVE_NAN \ _mcDec128ConstFromParts (0, _mcDec128QuietNaNCombo | 1ull << 63) -/// Convert an mc_dec128 value to a DFP 128-bit object -static inline BID_UINT128 -_mc_to_bid128 (mc_dec128 d) +/// Convert an mc_dec128 value to the DFP library's type +static inline _mc_dec128_underlying +_mc_to_dfp (mc_dec128 d) { - BID_UINT128 r; + _mc_dec128_underlying r; memcpy (&r, &d, sizeof d); return r; } -/// Convert a DFP 128-bit object to a mc_dec128 value +/// Convert the DFP library's type to a mc_dec128 value static inline mc_dec128 -_bid128_to_mc (BID_UINT128 d) +_dfp_to_mc (_mc_dec128_underlying d) { mc_dec128 r; memcpy (&r, &d, sizeof d); return r; } +#if MC_HAVE_LIBDFP +// Store a floating-point environment, and the rounding mode. +struct _mc_fenv { + fenv_t fenv; + int rnd; +}; + +// Save the current floating-point environment and set the rounding mode +static inline int +_mc_push_fenv (int rnd, struct _mc_fenv *env) +{ + // Suspent the fenv: + int err = feholdexcept (&env->fenv); + assert (!err && "Error while holding floating-point environment"); + // Save the rounding mode: + env->rnd = fe_dec_getround (); + // swap the rounding mode: + switch (rnd) { + case MC_DEC128_ROUND_TOWARD_ZERO: + err = fe_dec_setround (FE_DEC_TOWARDZERO); + break; + case MC_DEC128_ROUND_NEAREST_AWAY: + err = fe_dec_setround (FE_DEC_TONEARESTFROMZERO); + break; + case MC_DEC128_ROUND_NEAREST_EVEN: + err = fe_dec_setround (FE_DEC_TONEAREST); + break; + case MC_DEC128_ROUND_DOWNWARD: + err = fe_dec_setround (FE_DEC_DOWNWARD); + break; + case MC_DEC128_ROUND_UPWARD: + err = fe_dec_setround (FE_DEC_UPWARD); + break; + default: + // No constant set, so do nothing. + break; + } + assert (!err && "Error while reading floating-point rounding mode"); + (void) err; + return 1; +} + +// Restore the previously saved mode and environment: +static inline void +_mc_pop_fenv (struct _mc_fenv *env, mc_dec128_flagset *flags) +{ + // Get current exceptions: + fexcept_t exc; + int err = fegetexceptflag (&exc, FE_ALL_EXCEPT); + assert (!err && "Error while reading floating-point exceptions"); + if (flags) { + // Caller wants them: + flags->bits = (int) exc; + } else { + // Caller doesn't want to check them, so fire a signal if applicable: + if (exc & (FE_DIVBYZERO | FE_INVALID)) { + fprintf (stderr, "Unhandled floating-point exception %d\n", exc); + fflush (stderr); + raise (SIGFPE); + } + } + // Restore the rounding mode: + err = fe_dec_setround (env->rnd); + assert (!err && "Error while restoring rounding mode"); + // Restore the fenv: + err = feupdateenv (&env->fenv); + assert (!err && "Error while restoring floating-point env"); + (void) err; +} + +// clang-format off +/** + * @brief Suspend the current floating-point environment and set the rounding mode. + * + * @param Rounding Set the roundin mode for Decimal128, or `-1` to not set a new mode. + * @param FlagsPtr When restoring the environment, write floating-point exceptions through this pointer. + * + * Use this macro as a prefix to a compound statement. The compound statement will have the body protected. + * + * @note If `FlagsPtr` is NULL and an FE_DIVBYZERO or FE_INVALID exception occurs, SIGFPE will be raised. + * @note DO NOT `return` from within the block. + */ +#define MC_HOLD_FENV(Rounding, FlagsPtr) \ + for (int _fe_once = 1; _fe_once; _fe_once = 0) \ + for (struct _mc_fenv _fe_env; _fe_once && _mc_push_fenv(Rounding, &_fe_env); _fe_once = 0, _mc_pop_fenv(&_fe_env, FlagsPtr)) \ + for (; _fe_once; _fe_once = 0) +// clang-format on +#endif /// libdfp + /** * @brief Convert a double-precision binary floating point value into the * nearest Decimal128 value @@ -156,9 +275,20 @@ mc_dec128_from_double_ex (double d, mc_dec128_rounding_mode rnd, mc_dec128_flagset *flags) { - mc_dec128_flagset zero_flags = {0}; - return _bid128_to_mc ( - binary64_to_bid128 (d, rnd, flags ? &flags->bits : &zero_flags.bits)); + MC_IF_IntelDFP ({ + mc_dec128_flagset zero_flags = {0}; + return _dfp_to_mc ( + binary64_to_bid128 (d, rnd, flags ? &flags->bits : &zero_flags.bits)); + }); + MC_IF_LIBDFP ({ + mc_dec128 r; + MC_HOLD_FENV (rnd, flags) + { + // GCC will handle the double → Decimal128 promotion + r = _dfp_to_mc (d); + } + return r; + }); } /** @@ -179,15 +309,10 @@ mc_dec128_from_double (double d) * @param rnd The rounding mode to use if the result is not representable * @param flags Out param for exception/error flags (Optional) */ -static inline mc_dec128 +extern mc_dec128 mc_dec128_from_string_ex (const char *s, mc_dec128_rounding_mode rnd, - mc_dec128_flagset *flags) -{ - mc_dec128_flagset zero_flags = {0}; - return _bid128_to_mc (bid128_from_string ( - (char *) s, rnd, flags ? &flags->bits : &zero_flags.bits)); -} + mc_dec128_flagset *flags); /** * @brief Convert a string representation of a number into the nearest @@ -214,15 +339,8 @@ typedef struct mc_dec128_string { * @param d The number to represent * @param flags Output parameter for exception/error flags (optional) */ -static inline mc_dec128_string -mc_dec128_to_string_ex (mc_dec128 d, mc_dec128_flagset *flags) -{ - mc_dec128_flagset zero_flags = {0}; - mc_dec128_string out = {{0}}; - bid128_to_string ( - out.str, _mc_to_bid128 (d), flags ? &flags->bits : &zero_flags.bits); - return out; -} +extern mc_dec128_string +mc_dec128_to_string_ex (mc_dec128 d, mc_dec128_flagset *flags); /** * @brief Render a Decimal128 value as a string (in engineering notation) @@ -234,63 +352,76 @@ mc_dec128_to_string (mc_dec128 d) } /// Compare two dec128 numbers -#define DECL_IDF_COMPARE_1(Oper) \ - static inline bool mc_dec128_##Oper##_ex ( \ - mc_dec128 left, mc_dec128 right, mc_dec128_flagset *flags) \ - { \ - mc_dec128_flagset zero_flags = {0}; \ - return 0 != \ - bid128_quiet_##Oper (_mc_to_bid128 (left), \ - _mc_to_bid128 (right), \ - flags ? &flags->bits : &zero_flags.bits); \ - } \ - \ - static inline bool mc_dec128_##Oper (mc_dec128 left, mc_dec128 right) \ - { \ - return mc_dec128_##Oper##_ex (left, right, NULL); \ +#define DECL_IDF_COMPARE_1(Oper, Builtin) \ + static inline bool mc_dec128_##Oper##_ex ( \ + mc_dec128 left, mc_dec128 right, mc_dec128_flagset *flags) \ + { \ + _mc_dec128_underlying l = _mc_to_dfp (left), r = _mc_to_dfp (right); \ + MC_IF_IntelDFP ({ \ + mc_dec128_flagset zero_flags = {0}; \ + return 0 != bid128_quiet_##Oper ( \ + l, r, flags ? &flags->bits : &zero_flags.bits); \ + }); \ + MC_IF_LIBDFP ({ \ + bool r; \ + MC_HOLD_FENV (-1, flags) \ + { \ + r = l Builtin r; \ + } \ + return r; \ + }); \ + } \ + \ + static inline bool mc_dec128_##Oper (mc_dec128 left, mc_dec128 right) \ + { \ + return mc_dec128_##Oper##_ex (left, right, NULL); \ } -#define DECL_IDF_COMPARE(Op) DECL_IDF_COMPARE_1 (Op) -DECL_IDF_COMPARE (equal) -DECL_IDF_COMPARE (not_equal) -DECL_IDF_COMPARE (greater) -DECL_IDF_COMPARE (greater_equal) -DECL_IDF_COMPARE (less) -DECL_IDF_COMPARE (less_equal) +#define DECL_IDF_COMPARE(Op, Builtin) DECL_IDF_COMPARE_1 (Op, Builtin) + +DECL_IDF_COMPARE (equal, ==) +DECL_IDF_COMPARE (not_equal, !=) +DECL_IDF_COMPARE (greater, >) +DECL_IDF_COMPARE (greater_equal, >=) +DECL_IDF_COMPARE (less, <) +DECL_IDF_COMPARE (less_equal, <=) #undef DECL_IDF_COMPARE #undef DECL_IDF_COMPARE_1 /// Test properties of Decimal128 numbers -#define DECL_PREDICATE(Name, BIDName) \ - static inline bool mc_dec128_##Name (mc_dec128 d) \ - { \ - return 0 != bid128_##BIDName (_mc_to_bid128 (d)); \ +#define DECL_PREDICATE(Name, BIDName) \ + static inline bool mc_dec128_##Name (mc_dec128 d) \ + { \ + _mc_dec128_underlying v = _mc_to_dfp (d); \ + MC_IF_IntelDFP ({ return 0 != bid128_##BIDName (v); }); \ + MC_IF_LIBDFP ({ return 0 != Name##d128 (v); }); \ } -DECL_PREDICATE (is_zero, isZero) -DECL_PREDICATE (is_negative, isSigned) -DECL_PREDICATE (is_inf, isInf) -DECL_PREDICATE (is_finite, isFinite) -DECL_PREDICATE (is_nan, isNaN) +DECL_PREDICATE (isinf, isInf) +DECL_PREDICATE (isfinite, isFinite) +DECL_PREDICATE (isnan, isNaN) #undef DECL_PREDICATE /// Binary arithmetic operations on two Decimal128 numbers -#define DECL_IDF_BINOP_WRAPPER(Oper) \ +#define DECL_IDF_BINOP_WRAPPER(Oper, Builtin) \ static inline mc_dec128 mc_dec128_##Oper##_ex ( \ mc_dec128 left, \ mc_dec128 right, \ mc_dec128_rounding_mode mode, \ mc_dec128_flagset *flags) \ { \ - mc_dec128_flagset zero_flags = {0}; \ - return _bid128_to_mc ( \ - bid128_##Oper (_mc_to_bid128 (left), \ - _mc_to_bid128 (right), \ - mode, \ - flags ? &flags->bits : &zero_flags.bits)); \ + _mc_dec128_underlying l = _mc_to_dfp (left), r = _mc_to_dfp (right); \ + _mc_dec128_underlying ret; \ + MC_IF_IntelDFP ({ \ + mc_dec128_flagset zero_flags = {0}; \ + ret = bid128_##Oper ( \ + l, r, mode, flags ? &flags->bits : &zero_flags.bits); \ + }); \ + MC_IF_LIBDFP (MC_HOLD_FENV (mode, flags) { ret = l Builtin r; }); \ + return _dfp_to_mc (ret); \ } \ \ static inline mc_dec128 mc_dec128_##Oper (mc_dec128 left, mc_dec128 right) \ @@ -299,11 +430,10 @@ DECL_PREDICATE (is_nan, isNaN) left, right, MC_DEC128_ROUND_DEFAULT, NULL); \ } -DECL_IDF_BINOP_WRAPPER (add) -DECL_IDF_BINOP_WRAPPER (mul) -DECL_IDF_BINOP_WRAPPER (div) -DECL_IDF_BINOP_WRAPPER (sub) -DECL_IDF_BINOP_WRAPPER (pow) +DECL_IDF_BINOP_WRAPPER (add, +) +DECL_IDF_BINOP_WRAPPER (mul, *) +DECL_IDF_BINOP_WRAPPER (div, /) +DECL_IDF_BINOP_WRAPPER (sub, -) #undef DECL_IDF_BINOP_WRAPPER @@ -312,11 +442,15 @@ DECL_IDF_BINOP_WRAPPER (pow) static inline mc_dec128 mc_dec128_##Oper##_ex (mc_dec128 operand, \ mc_dec128_flagset *flags) \ { \ - mc_dec128_flagset zero_flags = {0}; \ - return _bid128_to_mc ( \ - bid128_##Oper (_mc_to_bid128 (operand), \ - MC_DEC128_ROUND_DEFAULT, \ - flags ? &flags->bits : &zero_flags.bits)); \ + _mc_dec128_underlying ret, op = _mc_to_dfp (operand); \ + MC_IF_LIBDFP (MC_HOLD_FENV (-1, flags) { ret = Oper##d128 (op); }); \ + MC_IF_IntelDFP ({ \ + mc_dec128_flagset zero_flags = {0}; \ + ret = bid128_##Oper (op, \ + MC_DEC128_ROUND_DEFAULT, \ + flags ? &flags->bits : &zero_flags.bits); \ + }); \ + return _dfp_to_mc (ret); \ } \ \ static inline mc_dec128 mc_dec128_##Oper (mc_dec128 operand) \ @@ -333,35 +467,47 @@ mc_dec128_round_integral_ex (mc_dec128 value, mc_dec128_rounding_mode direction, mc_dec128_flagset *flags) { - BID_UINT128 bid = _mc_to_bid128 (value); - mc_dec128_flagset zero_flags = {0}; - _IDEC_flags *fl = flags ? &flags->bits : &zero_flags.bits; - switch (direction) { - case MC_DEC128_ROUND_TOWARD_ZERO: - return _bid128_to_mc (bid128_round_integral_zero (bid, fl)); - case MC_DEC128_ROUND_NEAREST_AWAY: - return _bid128_to_mc (bid128_round_integral_nearest_away (bid, fl)); - case MC_DEC128_ROUND_NEAREST_EVEN: - return _bid128_to_mc (bid128_round_integral_nearest_even (bid, fl)); - case MC_DEC128_ROUND_DOWNWARD: - return _bid128_to_mc (bid128_round_integral_negative (bid, fl)); - case MC_DEC128_ROUND_UPWARD: - return _bid128_to_mc (bid128_round_integral_positive (bid, fl)); - default: - abort (); - } + MC_IF_IntelDFP ({ + BID_UINT128 bid = _mc_to_dfp (value); + mc_dec128_flagset zero_flags = {0}; + _IDEC_flags *fl = flags ? &flags->bits : &zero_flags.bits; + switch (direction) { + case MC_DEC128_ROUND_TOWARD_ZERO: + return _dfp_to_mc (bid128_round_integral_zero (bid, fl)); + case MC_DEC128_ROUND_NEAREST_AWAY: + return _dfp_to_mc (bid128_round_integral_nearest_away (bid, fl)); + case MC_DEC128_ROUND_NEAREST_EVEN: + return _dfp_to_mc (bid128_round_integral_nearest_even (bid, fl)); + case MC_DEC128_ROUND_DOWNWARD: + return _dfp_to_mc (bid128_round_integral_negative (bid, fl)); + case MC_DEC128_ROUND_UPWARD: + return _dfp_to_mc (bid128_round_integral_positive (bid, fl)); + default: + abort (); + } + }); + MC_IF_LIBDFP ({ + _Decimal128 d = _mc_to_dfp (value); + MC_HOLD_FENV (direction, flags) + { + d = rintd128 (d); + } + return _dfp_to_mc (d); + }); } static inline mc_dec128 mc_dec128_negate (mc_dec128 operand) { - return _bid128_to_mc (bid128_negate (_mc_to_bid128 (operand))); + MC_IF_IntelDFP (return _dfp_to_mc (bid128_negate (_mc_to_dfp (operand)));); + MC_IF_LIBDFP (return _dfp_to_mc (-_mc_to_dfp (operand))); } static inline mc_dec128 mc_dec128_abs (mc_dec128 operand) { - return _bid128_to_mc (bid128_abs (_mc_to_bid128 (operand))); + MC_IF_IntelDFP (return _dfp_to_mc (bid128_abs (_mc_to_dfp (operand)))); + MC_IF_LIBDFP (return _dfp_to_mc (fabsd128 (_mc_to_dfp (operand)))); } /** @@ -379,12 +525,18 @@ mc_dec128_scale_ex (mc_dec128 fac, mc_dec128_rounding_mode rounding, mc_dec128_flagset *flags) { - mc_dec128_flagset zero_flags = {0}; - return _bid128_to_mc ( - bid128_scalbln (_mc_to_bid128 (fac), - exp, - rounding, - flags ? &flags->bits : &zero_flags.bits)); + _mc_dec128_underlying ret; + MC_IF_LIBDFP (MC_HOLD_FENV (rounding, flags) { + ret = scalblnd128 (_mc_to_dfp (fac), exp); + }); + MC_IF_IntelDFP ({ + mc_dec128_flagset zero_flags = {0}; + ret = bid128_scalbln (_mc_to_dfp (fac), + exp, + rounding, + flags ? &flags->bits : &zero_flags.bits); + }); + return _dfp_to_mc (ret); } /** @@ -420,12 +572,16 @@ typedef struct mc_dec128_modf_result { static inline mc_dec128_modf_result mc_dec128_modf_ex (mc_dec128 d, mc_dec128_flagset *flags) { - mc_dec128_flagset zero_flags = {0}; + _mc_dec128_underlying frac, whole; + _mc_dec128_underlying val = _mc_to_dfp (d); + MC_IF_LIBDFP (MC_HOLD_FENV (-1, flags) { frac = modfd128 (val, &whole); }); + MC_IF_IntelDFP ({ + mc_dec128_flagset zero_flags = {0}; + frac = bid128_modf (val, &whole, flags ? &flags->bits : &zero_flags.bits); + }); mc_dec128_modf_result res; - BID_UINT128 whole; - res.frac = _bid128_to_mc (bid128_modf ( - _mc_to_bid128 (d), &whole, flags ? &flags->bits : &zero_flags.bits)); - res.whole = _bid128_to_mc (whole); + res.frac = _dfp_to_mc (frac); + res.whole = _dfp_to_mc (whole); return res; } @@ -454,10 +610,14 @@ mc_dec128_modf (mc_dec128 d) static inline mc_dec128 mc_dec128_fmod_ex (mc_dec128 numer, mc_dec128 denom, mc_dec128_flagset *flags) { - mc_dec128_flagset zero_flags = {0}; - return _bid128_to_mc (bid128_fmod (_mc_to_bid128 (numer), - _mc_to_bid128 (denom), - flags ? &flags->bits : &zero_flags.bits)); + _mc_dec128_underlying ret; + _mc_dec128_underlying num = _mc_to_dfp (numer), den = _mc_to_dfp (denom); + MC_IF_LIBDFP (MC_HOLD_FENV (-1, flags) { ret = fmodd128 (num, den); }); + MC_IF_IntelDFP ({ + mc_dec128_flagset zero_flags = {0}; + ret = bid128_fmod (num, den, flags ? &flags->bits : &zero_flags.bits); + }); + return _dfp_to_mc (ret); } /** @@ -483,9 +643,16 @@ mc_dec128_fmod (mc_dec128 numer, mc_dec128 denom) static inline int64_t mc_dec128_to_int64_ex (mc_dec128 d, mc_dec128_flagset *flags) { - mc_dec128_flagset zero_flags = {0}; - return bid128_to_int64_int (_mc_to_bid128 (d), - flags ? &flags->bits : &zero_flags.bits); + int64_t ret; + _mc_dec128_underlying v = _mc_to_dfp (d); + MC_IF_LIBDFP (MC_HOLD_FENV (MC_DEC128_ROUND_TOWARD_ZERO, flags) { + ret = llrintd128 (v); + }); + MC_IF_IntelDFP ({ + mc_dec128_flagset zero_flags = {0}; + ret = bid128_to_int64_int (v, flags ? &flags->bits : &zero_flags.bits); + }); + return ret; } /** @@ -577,6 +744,34 @@ mc_dec128_coeff (mc_dec128 d) return mlib_int128_add (hi_128, MLIB_INT128_CAST (lo)); } +/** + * @brief Test if a number is a normal zero + * + * @retval true If `d` is non-NaN and non-Inf and has a zero value, regardless + * of sign or exponent + * @retval false Otherwise + */ +static inline bool +mc_dec128_is_zero (mc_dec128 d) +{ + mlib_int128 coeff = mc_dec128_coeff (d); + return !mc_dec128_isnan (d) && mc_dec128_isfinite (d) && + mlib_int128_eq (coeff, MLIB_INT128 (0)); +} + +/** + * @brief Test if a number is a normal negative (has the sign-bit set) + * + * @retval true If `d` is not NaN nor Inf and has the sign-bit set + * @retval false Otherwise + */ +static inline bool +mc_dec128_is_negative (mc_dec128 d) +{ + uint64_t hi = d._words[MLIB_IS_LITTLE_ENDIAN ? 1 : 0]; + return !mc_dec128_isnan (d) && mc_dec128_isfinite (d) && (hi & (1ull << 63)); +} + /** * @brief Obtain the biased value of the Decimal128 exponent. * @@ -600,110 +795,8 @@ mc_dec128_get_biased_exp (mc_dec128 d) } /// Create a decimal string from a dec128 number. The result must be freed. -static inline char * -mc_dec128_to_new_decimal_string (mc_dec128 d) -{ - if (mc_dec128_is_zero (d)) { - // Just return "0" - char *s = (char *) calloc (2, 1); - if (s) { - s[0] = '0'; - } - return s; - } - - if (mc_dec128_is_negative (d)) { - // Negate the result, return a string with a '-' prefix - d = mc_dec128_negate (d); - char *s = mc_dec128_to_new_decimal_string (d); - if (!s) { - return NULL; - } - char *s1 = (char *) calloc (strlen (s) + 2, 1); - if (s1) { - s1[0] = '-'; - strcpy (s1 + 1, s); - } - free (s); - return s1; - } - - if (mc_dec128_is_inf (d) || mc_dec128_is_nan (d)) { - const char *r = mc_dec128_is_inf (d) ? "Infinity" : "NaN"; - char *c = (char *) calloc (strlen (r) + 1, 1); - if (c) { - strcpy (c, r); - } - return c; - } - - const char DIGITS[] = "0123456789"; - const mc_dec128 TEN = MC_DEC128_C (10); - - // Format the whole and fractional part separately. - mc_dec128_modf_result modf = mc_dec128_modf (d); - - if (mc_dec128_is_zero (modf.frac)) { - // This is a non-zero integer - // Allocate enough digits: - mc_dec128 log10 = mc_dec128_modf (mc_dec128_log10 (d)).whole; - int64_t ndigits = mc_dec128_to_int64 (log10) + 1; - // +1 for null - char *strbuf = (char *) calloc ((size_t) (ndigits + 1), 1); - if (strbuf) { - // Write the string backwards: - char *optr = strbuf + ndigits - 1; - while (!mc_dec128_is_zero (modf.whole)) { - mc_dec128 rem = mc_dec128_fmod (modf.whole, TEN); - int64_t remi = mc_dec128_to_int64 (rem); - *optr-- = DIGITS[remi]; - // Divide ten - modf = mc_dec128_modf (mc_dec128_div (modf.whole, TEN)); - } - } - return strbuf; - } else if (mc_dec128_is_zero (modf.whole)) { - // This is only a fraction (less than one, but more than zero) - while (!mc_dec128_is_zero (mc_dec128_modf (d).frac)) { - d = mc_dec128_mul (d, TEN); - } - // 'd' is now a whole number - char *part = mc_dec128_to_new_decimal_string (d); - if (!part) { - return NULL; - } - char *buf = (char *) calloc (strlen (part) + 3, 1); - if (buf) { - buf[0] = '0'; - buf[1] = '.'; - strcpy (buf + 2, part); - } - free (part); - return buf; - } else { - // We have both a whole part and a fractional part - char *whole = mc_dec128_to_new_decimal_string (modf.whole); - if (!whole) { - return NULL; - } - char *frac = mc_dec128_to_new_decimal_string (modf.frac); - if (!frac) { - free (whole); - return NULL; - } - char *ret = (char *) calloc (strlen (whole) + strlen (frac) + 1, 1); - if (ret) { - char *out = ret; - strcpy (out, whole); - out += strlen (whole); - // "frac" contains a leading zero, which we don't want - strcpy (out, frac + 1); - } - free (whole); - free (frac); - return ret; - } -} +extern char * +mc_dec128_to_new_decimal_string (mc_dec128 d); static inline mc_dec128 mc_dec128_from_bson_iter (bson_iter_t *it) diff --git a/src/mc-dec128.test.cpp b/src/mc-dec128.test.cpp index b5bd0bebc..10674905f 100644 --- a/src/mc-dec128.test.cpp +++ b/src/mc-dec128.test.cpp @@ -1,6 +1,15 @@ +// Required to enable decimal for libdfp in C++ +#define __STDC_WANT_DEC_FP__ 1 +#define __STDC_WANT_IEC_60559_DFP_EXT__ 1 + +#if __has_include() +#include +#endif + #include -#include +#include +#include #include #define CHECK MLIB_CHECK @@ -57,16 +66,18 @@ main () c = (c + MC_DEC128_C (1)) / MC_DEC128_C (2); CHECK (c == mc_dec128_from_string ("2.5")); + //* Alas, different dfp implementations render strings differently, so we + // just check that it rendered anything at all. mc_dec128_string s = mc_dec128_to_string (c); - CHECK (std::string (s.str) == "+25E-1"); + CHECK (std::strlen (s.str) > 0); char *str = mc_dec128_to_new_decimal_string (c); CHECK (std::string (str) == "2.5"); free (str); mc_dec128 infin = MC_DEC128_POSITIVE_INFINITY; - CHECK (mc_dec128_is_inf (infin)); + CHECK (mc_dec128_isinf (infin)); mc_dec128 nan = MC_DEC128_POSITIVE_NAN; - CHECK (mc_dec128_is_nan (nan)); + CHECK (mc_dec128_isnan (nan)); } diff --git a/src/mc-range-encoding.c b/src/mc-range-encoding.c index 67e29de9b..51e8de74e 100644 --- a/src/mc-range-encoding.c +++ b/src/mc-range-encoding.c @@ -325,8 +325,8 @@ static mlib_int128 dec128_to_int128 (mc_dec128 dec) { // Only normal numbers - BSON_ASSERT (mc_dec128_is_finite (dec)); - BSON_ASSERT (!mc_dec128_is_nan (dec)); + BSON_ASSERT (mc_dec128_isfinite (dec)); + BSON_ASSERT (!mc_dec128_isnan (dec)); // We don't support negative numbers BSON_ASSERT (!mc_dec128_is_negative (dec)); // There is no fractional part: @@ -362,7 +362,7 @@ mc_getTypeInfoDecimal128 (mc_getTypeInfoDecimal128_args_t args, } // We only accept normal numbers - if (mc_dec128_is_inf (args.value) || mc_dec128_is_nan (args.value)) { + if (mc_dec128_isinf (args.value) || mc_dec128_isnan (args.value)) { CLIENT_ERR ("Infinity and Nan Decimal128 values are not supported."); return false; } @@ -423,7 +423,7 @@ mc_getTypeInfoDecimal128 (mc_getTypeInfoDecimal128_args_t args, // We can overflow if max = max_dec128 and min = min_dec128 so make sure // we have finite number after we do subtraction - if (mc_dec128_is_finite (bounds)) { + if (mc_dec128_isfinite (bounds)) { // This creates a range which is wider then we permit by our min/max // bounds check with the +1 but it is as the algorithm is written in // WRITING-11907. @@ -433,7 +433,7 @@ mc_getTypeInfoDecimal128 (mc_getTypeInfoDecimal128_args_t args, /// precision (as decimal) mc_dec128 bits_range_dec = mc_dec128_log2 (precision_scaled_bounds); - if (mc_dec128_is_finite (bits_range_dec) && + if (mc_dec128_isfinite (bits_range_dec) && mc_dec128_less (bits_range_dec, MC_DEC128 (128))) { // We need fewer than 128 bits to hold the result. But round up, // just to be sure: @@ -494,7 +494,8 @@ mc_getTypeInfoDecimal128 (mc_getTypeInfoDecimal128_args_t args, v_prime2 = mc_dec128_round_integral_ex ( v_prime2, MC_DEC128_ROUND_TOWARD_ZERO, NULL); - BSON_ASSERT (mc_dec128_less (mc_dec128_log2 (v_prime2), MC_DEC128 (128))); + BSON_ASSERT (mc_dec128_is_zero (v_prime2) || + mc_dec128_less (mc_dec128_log2 (v_prime2), MC_DEC128 (128))); // Resulting OST maximum mlib_int128 ost_max = From ae0353db18e40bf88fbb3c6a73c341c6b013416c Mon Sep 17 00:00:00 2001 From: vector-of-bool Date: Thu, 9 Feb 2023 03:43:44 +0000 Subject: [PATCH 03/12] Fix missed test cases due to tricky macro expansions --- test/test-mc-range-encoding.c | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/test/test-mc-range-encoding.c b/test/test-mc-range-encoding.c index f9dfbcc91..e6d00989c 100644 --- a/test/test-mc-range-encoding.c +++ b/test/test-mc-range-encoding.c @@ -766,13 +766,11 @@ _test_RangeTest_Encode_Decimal128 (_mongocrypt_tester_t *tester) .expect = MLIB_INT128_CAST (Expect), \ } -#define ASSERT_EIBB_OVERFLOW(Val, Max, Min, Precision, Expect) \ - (Decimal128Test) \ - { \ - .value = mc_dec128_from_string (#Val), \ - .min = OPT_MC_DEC128 (mc_dec128_from_string (#Min)), \ - .max = OPT_MC_DEC128 (mc_dec128_from_string (#Max)), \ - .precision = OPT_U32 (Precision), .expect = Expect, \ +#define ASSERT_EIBB_OVERFLOW(Val, Max, Min, Precision, Expect) \ + (Decimal128Test) \ + { \ + .value = Val, .min = OPT_MC_DEC128 (Min), .max = OPT_MC_DEC128 (Max), \ + .precision = OPT_U32 (Precision), .expect = Expect, \ } ASSERT_EIBB (0, 1, -1, 3, 1000), @@ -781,16 +779,16 @@ _test_RangeTest_Encode_Decimal128 (_mongocrypt_tester_t *tester) ASSERT_EIBB (-1E-33, 1, -1E5, 3, 100000000), ASSERT_EIBB_OVERFLOW ( - 0, + MC_DEC128_ZERO, MC_DEC128_LARGEST_POSITIVE, MC_DEC128_LARGEST_NEGATIVE, 3, mlib_int128_from_string ("170141183460469231731687303715884105728", NULL)), ASSERT_EIBB_OVERFLOW ( - 0, - DBL_MAX, - DBL_MIN, + MC_DEC128_ZERO, + mc_dec128_from_double (DBL_MAX), + mc_dec128_from_double (-DBL_MAX), 3, mlib_int128_from_string ("170141183460469231731687303715884105728", NULL)), @@ -806,9 +804,9 @@ _test_RangeTest_Encode_Decimal128 (_mongocrypt_tester_t *tester) ASSERT_EIBB (-5, -1, -10, 3, 5000), ASSERT_EIBB_OVERFLOW ( - 1E100, - DBL_MAX, - DBL_MIN, + mc_dec128_from_string ("1E100"), + mc_dec128_from_double (DBL_MAX), + mc_dec128_from_double (DBL_MIN), 3, mlib_int128_from_string ("232572183460469231731687303715884099485", NULL)), From 07f4e80a7c5498141661d018edfc810455a16cb3 Mon Sep 17 00:00:00 2001 From: vector-of-bool Date: Thu, 9 Feb 2023 03:44:14 +0000 Subject: [PATCH 04/12] Fix CTest tests based on target names --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e67e1cc56..5f292cba5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -522,7 +522,7 @@ add_test ( foreach (test IN ITEMS path str) add_executable (mlib.${test}.test src/mlib/${test}.test.c) - add_test (mlib.${test} mlib.${test}.test) + add_test (NAME mlib.${test} COMMAND mlib.${test}.test) target_link_libraries (mlib.${test}.test PRIVATE mongo::mlib) endforeach () @@ -546,7 +546,7 @@ if ("cxx_relaxed_constexpr" IN_LIST CMAKE_CXX_COMPILE_FEATURES) Threads::Threads _mongocrypt::libbson_for_static ) - add_test ("${test_name}" "${exe_name}") + add_test (NAME "${test_name}" COMMAND "${exe_name}") endforeach () endif () From eaf585530be484ae8d7e360f76c27cb5cd3f9ffd Mon Sep 17 00:00:00 2001 From: vector-of-bool Date: Thu, 9 Feb 2023 03:49:01 +0000 Subject: [PATCH 05/12] Update packaging deps --- debian/control | 3 +-- etc/rpm/tweak.awk | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/debian/control b/debian/control index 97924eb94..4343b61c0 100644 --- a/debian/control +++ b/debian/control @@ -9,7 +9,7 @@ Build-Depends: debhelper (>= 10), quilt, libssl-dev, pkg-config, - libintelrdfpmath-dev (>= 2.0u2-6), + libdfp-dev (>= 1.0.16-1), libbson-dev Standards-Version: 4.6.2 Section: libs @@ -21,7 +21,6 @@ Architecture: any Multi-Arch: same Depends: libbson-dev, libmongocrypt0 (= ${binary:Version}), - libintelrdfpmath-dev (>= 2.0u2-6), ${misc:Depends} Description: client-side field level encryption library - dev files libmongocrypt facilitates the client-side encryption and decryption, diff --git a/etc/rpm/tweak.awk b/etc/rpm/tweak.awk index c4c724ae6..d5db2560e 100644 --- a/etc/rpm/tweak.awk +++ b/etc/rpm/tweak.awk @@ -8,6 +8,20 @@ # Drop the source. We're bringing our own /Source0/ { next } +/%package devel/ { + # Insert a dep on 'libdfp-devel' + print + print "BuildRequires: libdfp-devel" + print "Requires: libdfp" + next +} + +/-DMONGOCRYPT_MONGOC_DIR:STRING=USE-SYSTEM/ { + print + print " -DMONGOCRYPT_DFP_DIR=USE-SYSTEM \\" + next +} + # Replace the autosetup with one that copies the source tree into place /%autosetup/ { print "cp -rf %{_sourcedir}/. %{_builddir}/\n" \ From e5503ff8f85a470f908b4086e6096d13e0393b1e Mon Sep 17 00:00:00 2001 From: vector-of-bool Date: Thu, 9 Feb 2023 03:59:19 +0000 Subject: [PATCH 06/12] tmp --- src/mc-dec128.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mc-dec128.h b/src/mc-dec128.h index 4fc2d7738..5a2990622 100644 --- a/src/mc-dec128.h +++ b/src/mc-dec128.h @@ -363,7 +363,7 @@ mc_dec128_to_string (mc_dec128 d) l, r, flags ? &flags->bits : &zero_flags.bits); \ }); \ MC_IF_LIBDFP ({ \ - bool r; \ + bool r = false; \ MC_HOLD_FENV (-1, flags) \ { \ r = l Builtin r; \ From e0eddf19bdff1ef012f0c7ded3c3e97b994e87f7 Mon Sep 17 00:00:00 2001 From: vector-of-bool Date: Thu, 9 Feb 2023 20:33:33 +0000 Subject: [PATCH 07/12] Install libdfp deps in Earth package builds --- Earthfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Earthfile b/Earthfile index 81b585b38..f63ecc9f5 100644 --- a/Earthfile +++ b/Earthfile @@ -253,7 +253,7 @@ rpm-build: GIT CLONE https://src.fedoraproject.org/rpms/libmongocrypt.git /R # Install the packages listed by "BuildRequires" and rpm-build: RUN __install $(awk '/^BuildRequires:/ { print $2 }' /R/libmongocrypt.spec) \ - rpm-build + rpm-build libdfp-devel DO +COPY_SOURCE RUN cp -r /s/libmongocrypt/. /R RUN awk -f /R/etc/rpm/tweak.awk < /R/libmongocrypt.spec > /R/libmongocrypt.2.spec @@ -300,7 +300,7 @@ deb-build: ARG env=deb-unstable FROM +env.$env RUN __install git-buildpackage fakeroot debhelper cmake libbson-dev \ - libintelrdfpmath-dev + libdfp-dev quilt DO +COPY_SOURCE WORKDIR /s/libmongocrypt RUN git clean -fdx && git reset --hard From 0a5a54a0873cfce176cd87874acb1bba2740ebe9 Mon Sep 17 00:00:00 2001 From: vector-of-bool Date: Thu, 9 Feb 2023 14:29:22 -0700 Subject: [PATCH 08/12] MSVC/Windows fixes --- src/mc-dec128.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/mc-dec128.h b/src/mc-dec128.h index 5a2990622..3270fd527 100644 --- a/src/mc-dec128.h +++ b/src/mc-dec128.h @@ -14,6 +14,10 @@ #include #include +// IntelDFP tries to be "helpful" and define fenv types on behalf of fenv.h, but +// it doesn't detect prior inclusion correctly in some cases. Define +// a header-guard macro that it looks for: +#define __FENV_H_INCLUDED #ifdef _DFP_MATH_H // We included libdfp's math.h @@ -55,8 +59,8 @@ typedef enum mc_dec128_rounding_mode { } mc_dec128_rounding_mode; typedef struct mc_dec128_flagset { - MC_IF_IntelDFP (_IDEC_flags bits); - MC_IF_LIBDFP (int bits); + MC_IF_IntelDFP (_IDEC_flags bits;) // + MC_IF_LIBDFP (int bits;) } mc_dec128_flagset; // This alignment conditional is the same conditions used in Intel's DFP From 0911c45ba57325c7a460fbcd855da5d8b30d365b Mon Sep 17 00:00:00 2001 From: vector-of-bool Date: Thu, 9 Feb 2023 14:30:16 -0700 Subject: [PATCH 09/12] Fix sign-compare warning on some GCCs --- src/mc-dec128.test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mc-dec128.test.cpp b/src/mc-dec128.test.cpp index 10674905f..f41f8d1df 100644 --- a/src/mc-dec128.test.cpp +++ b/src/mc-dec128.test.cpp @@ -69,7 +69,7 @@ main () //* Alas, different dfp implementations render strings differently, so we // just check that it rendered anything at all. mc_dec128_string s = mc_dec128_to_string (c); - CHECK (std::strlen (s.str) > 0); + CHECK (std::strlen (s.str) > 0u); char *str = mc_dec128_to_new_decimal_string (c); CHECK (std::string (str) == "2.5"); From df2ff5b9b5365d9cdf6a40c3014efdbaa83868c0 Mon Sep 17 00:00:00 2001 From: vector-of-bool Date: Thu, 9 Feb 2023 15:03:36 -0700 Subject: [PATCH 10/12] static-link for the dec128 test --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f292cba5..7c860cf9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -540,7 +540,7 @@ if ("cxx_relaxed_constexpr" IN_LIST CMAKE_CXX_COMPILE_FEATURES) add_executable ("${exe_name}" "${file}") target_compile_features ("${exe_name}" PRIVATE cxx_relaxed_constexpr) target_link_libraries ("${exe_name}" PRIVATE - mongocrypt + mongocrypt_static mongo::mlib mongocrypt::dfp Threads::Threads From 75556b38f53b04ca315669d39f643d8d1806b69e Mon Sep 17 00:00:00 2001 From: vector-of-bool Date: Thu, 9 Feb 2023 22:56:13 +0000 Subject: [PATCH 11/12] is not needed --- src/mc-dec128.test.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/mc-dec128.test.cpp b/src/mc-dec128.test.cpp index f41f8d1df..c431e1889 100644 --- a/src/mc-dec128.test.cpp +++ b/src/mc-dec128.test.cpp @@ -1,11 +1,3 @@ -// Required to enable decimal for libdfp in C++ -#define __STDC_WANT_DEC_FP__ 1 -#define __STDC_WANT_IEC_60559_DFP_EXT__ 1 - -#if __has_include() -#include -#endif - #include #include From e1680db8f2fcf9c9bf21b155c0d63dce4a7c185c Mon Sep 17 00:00:00 2001 From: vector-of-bool Date: Thu, 9 Feb 2023 23:51:27 +0000 Subject: [PATCH 12/12] Unused var warning --- src/mc-dec128.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mc-dec128.h b/src/mc-dec128.h index 3270fd527..e52bbda5d 100644 --- a/src/mc-dec128.h +++ b/src/mc-dec128.h @@ -367,12 +367,12 @@ mc_dec128_to_string (mc_dec128 d) l, r, flags ? &flags->bits : &zero_flags.bits); \ }); \ MC_IF_LIBDFP ({ \ - bool r = false; \ + bool ret = false; \ MC_HOLD_FENV (-1, flags) \ { \ - r = l Builtin r; \ + ret = l Builtin r; \ } \ - return r; \ + return ret; \ }); \ } \ \