diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -53,6 +53,8 @@ libc.src.stdlib.atoi libc.src.stdlib.atol libc.src.stdlib.atoll + libc.src.stdlib.strtod + libc.src.stdlib.strtof libc.src.stdlib.strtol libc.src.stdlib.strtoll libc.src.stdlib.strtoul diff --git a/libc/src/__support/CMakeLists.txt b/libc/src/__support/CMakeLists.txt --- a/libc/src/__support/CMakeLists.txt +++ b/libc/src/__support/CMakeLists.txt @@ -21,6 +21,7 @@ libc.include.errno libc.src.errno.__errno_location libc.utils.CPP.standalone_cpp + libc.src.__support.FPUtil.fputil ) add_header_library( diff --git a/libc/src/__support/str_conv_utils.h b/libc/src/__support/str_conv_utils.h --- a/libc/src/__support/str_conv_utils.h +++ b/libc/src/__support/str_conv_utils.h @@ -9,6 +9,7 @@ #ifndef LIBC_SRC_STDLIB_STDLIB_UTILS_H #define LIBC_SRC_STDLIB_STDLIB_UTILS_H +#include "src/__support/FPUtil/FPBits.h" #include "src/__support/ctype_utils.h" #include "utils/CPP/Limits.h" #include @@ -134,6 +135,177 @@ return is_positive ? static_cast(result) : -static_cast(result); } +// Takes a pointer to a string and a pointer to a string pointer. This function +// is used as the backend for all of the string to float functions. +template +static inline T strtofloatingpoint(const char *__restrict src, + char **__restrict str_end) { + fputil::FPBits result = fputil::FPBits(); + src = first_non_whitespace(src); + + using BitsType = typename fputil::FPBits::UIntType; + + if (*src == '+' || *src == '-') { + if (*src == '-') { + result.setSign(true); + } + ++src; + } + + static constexpr char DECIMAL_POINT = '.'; + static const char *INF_STRING = "infinity"; + static const char *NAN_STRING = "nan"; + + if (isdigit(*src)) { // regular number + int base = 10; + char exponent_marker = 'e'; + if (is_hex_start(src)) { + base = 16; + src += 2; + exponent_marker = 'p'; + } + bool after_decimal = false; + + BitsType mantissa = 0; + int32_t exponent = 0; + + // The goal for the first step of parsing is to convert the number in src to + // the format mantissa * (base ^ exponent) + + constexpr BitsType MANTISSA_MAX = + BitsType(1) << (fputil::FloatProperties::mantissaWidth + + 1); // The extra bit is to give space for the implicit 1 + while (isalnum(*src) || *src == DECIMAL_POINT) { + if (*src == DECIMAL_POINT && after_decimal) { + break; // this means that *src points to a second decimal point, ending + // the number. + } else if (*src == DECIMAL_POINT) { + after_decimal = true; + ++src; + continue; + } + int digit = b36_char_to_int(*src); + if (digit > base) { + break; + } + + if ((mantissa * base) + digit > MANTISSA_MAX) { + break; + } + + mantissa = (mantissa * base) + digit; + if (after_decimal) { + --exponent; + } + + ++src; + } + + // The second loop is to run through the remaining digits after we've filled + // the mantissa. + while (isalnum(*src) || *src == DECIMAL_POINT) { + if (*src == DECIMAL_POINT && after_decimal) { + break; // this means that *src points to a second decimal point, ending + // the number. + } else if (*src == DECIMAL_POINT) { + after_decimal = true; + ++src; + continue; + } + int digit = b36_char_to_int(*src); + if (digit > base) { + break; + } + + if (!after_decimal) { + exponent++; + } + + ++src; + } + + // if our base is 16 then convert the exponent to base 2 + if (base == 16) { + exponent *= 4; + } + + if ((*src | 32) == exponent_marker) { + if (*(src + 1) == '+' || *(src + 1) == '-' || isdigit(*(src + 1))) { + ++src; + char *temp_strend; + int32_t add_to_exponent = strtointeger(src, &temp_strend, 10); + src += temp_strend - src; + exponent += add_to_exponent; + } + } + + if (base == 16) { + while (mantissa < (MANTISSA_MAX >> 1)) { + mantissa = mantissa << 1; + --exponent; + } + // Account for the fact that the mantissa represented an integer + // previously, but now represents the fractional part of a normalized + // number. + exponent += fputil::FloatProperties::mantissaWidth; + + int32_t unbiased_exponent = exponent + fputil::FPBits::exponentBias; + if (unbiased_exponent < 0) { + // handle subnormals here + while (unbiased_exponent < 0 && mantissa > 0) { + mantissa = mantissa >> 1; + ++unbiased_exponent; + } + if (mantissa == 0) { + unbiased_exponent = 0; + } + } else if (unbiased_exponent > result.maxExponent) { + unbiased_exponent = result.maxExponent; + mantissa = 0; + } + + result.setUnbiasedExponent(unbiased_exponent); + result.setMantissa(mantissa); + } else { // base is 10 + } + + } else if ((*src | 32) == 'n') { // NaN + if ((src[1] | 32) == NAN_STRING[1] && (src[2] | 32) == NAN_STRING[2]) { + src += 3; + BitsType NaNMantissa = 0; + if (*src == '(') { + char *temp_src = 0; + if (isdigit(*(src + 1))) { + NaNMantissa = strtointeger(src + 1, &temp_src, 0); + if (*temp_src != ')') { + NaNMantissa = 0; + } else { + src = temp_src; + } + } + } + result.buildNaN(NaNMantissa); + } + } else if ((*src | 32) == 'i') { // INF + if ((src[1] | 32) == INF_STRING[1] && (src[2] | 32) == INF_STRING[2]) { + result.inf(); + if ((src[3] | 32) == INF_STRING[3] && (src[4] | 32) == INF_STRING[4] && + (src[5] | 32) == INF_STRING[5] && (src[6] | 32) == INF_STRING[6] && + (src[7] | 32) == INF_STRING[7]) { + // if the string is "INFINITY" then str_end needs to be set to src + 8. + src += 8; + } else { + src += 3; + } + } + } + + if (str_end != nullptr) + *str_end = const_cast(src); + + return T(result); +} + } // namespace internal } // namespace __llvm_libc diff --git a/libc/src/stdlib/CMakeLists.txt b/libc/src/stdlib/CMakeLists.txt --- a/libc/src/stdlib/CMakeLists.txt +++ b/libc/src/stdlib/CMakeLists.txt @@ -28,6 +28,26 @@ libc.src.__support.str_conv_utils ) +add_entrypoint_object( + strtof + SRCS + strtof.cpp + HDRS + strtof.h + DEPENDS + libc.src.__support.str_conv_utils +) + +add_entrypoint_object( + strtod + SRCS + strtod.cpp + HDRS + strtod.h + DEPENDS + libc.src.__support.str_conv_utils +) + add_entrypoint_object( strtol SRCS diff --git a/libc/src/stdlib/strtod.h b/libc/src/stdlib/strtod.h new file mode 100644 --- /dev/null +++ b/libc/src/stdlib/strtod.h @@ -0,0 +1,18 @@ +//===-- Implementation header for strtod ------------------------*- 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_STDLIB_STRTOD_H +#define LLVM_LIBC_SRC_STDLIB_STRTOD_H + +namespace __llvm_libc { + +double strtod(const char *__restrict str, char **__restrict str_end); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_STDLIB_STRTOD_H diff --git a/libc/src/stdlib/strtod.cpp b/libc/src/stdlib/strtod.cpp new file mode 100644 --- /dev/null +++ b/libc/src/stdlib/strtod.cpp @@ -0,0 +1,20 @@ +//===-- Implementation of strtod ------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "src/stdlib/strtod.h" +#include "src/__support/common.h" +#include "src/__support/str_conv_utils.h" + +namespace __llvm_libc { + +LLVM_LIBC_FUNCTION(double, strtod, + (const char *__restrict str, char **__restrict str_end)) { + return internal::strtofloatingpoint(str, str_end); +} + +} // namespace __llvm_libc diff --git a/libc/src/stdlib/strtof.h b/libc/src/stdlib/strtof.h new file mode 100644 --- /dev/null +++ b/libc/src/stdlib/strtof.h @@ -0,0 +1,18 @@ +//===-- Implementation header for strtof ------------------------*- 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_STDLIB_STRTOF_H +#define LLVM_LIBC_SRC_STDLIB_STRTOF_H + +namespace __llvm_libc { + +float strtof(const char *__restrict str, char **__restrict str_end); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_STDLIB_STRTOF_H diff --git a/libc/src/stdlib/strtof.cpp b/libc/src/stdlib/strtof.cpp new file mode 100644 --- /dev/null +++ b/libc/src/stdlib/strtof.cpp @@ -0,0 +1,20 @@ +//===-- Implementation of strtof ------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "src/stdlib/strtof.h" +#include "src/__support/common.h" +#include "src/__support/str_conv_utils.h" + +namespace __llvm_libc { + +LLVM_LIBC_FUNCTION(float, strtof, + (const char *__restrict str, char **__restrict str_end)) { + return internal::strtofloatingpoint(str, str_end); +} + +} // namespace __llvm_libc diff --git a/libc/test/src/stdlib/CMakeLists.txt b/libc/test/src/stdlib/CMakeLists.txt --- a/libc/test/src/stdlib/CMakeLists.txt +++ b/libc/test/src/stdlib/CMakeLists.txt @@ -30,6 +30,26 @@ libc.src.stdlib.atoll ) +add_libc_unittest( + strtod_test + SUITE + libc_stdlib_unittests + SRCS + strtod_test.cpp + DEPENDS + libc.src.stdlib.strtod +) + +add_libc_unittest( + strtof_test + SUITE + libc_stdlib_unittests + SRCS + strtof_test.cpp + DEPENDS + libc.src.stdlib.strtof +) + add_libc_unittest( strtol_test SUITE diff --git a/libc/test/src/stdlib/strtod_test.cpp b/libc/test/src/stdlib/strtod_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/stdlib/strtod_test.cpp @@ -0,0 +1,32 @@ +//===-- Unittests for strtod ---------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "src/stdlib/strtod.h" + +#include "utils/UnitTest/Test.h" + +#include +#include +#include + +TEST(LlvmLibcStrToDTest, JustPrint) { + const char *input = "10"; + char *str_end = nullptr; + + errno = 0; + double result = __llvm_libc::strtod(input, &str_end); + + void *indirection = &result; + unsigned long long raw_data = *(unsigned long long *)(indirection); + + unsigned int sign = raw_data >> 63; + int exponent = (raw_data >> 52) & 0x7ff; + unsigned long long mantissa = raw_data & 0xfffffffffffff; + + EXPECT_EQ(errno, 0); +} diff --git a/libc/test/src/stdlib/strtof_test.cpp b/libc/test/src/stdlib/strtof_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/stdlib/strtof_test.cpp @@ -0,0 +1,102 @@ +//===-- Unittests for strtof ---------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "src/stdlib/strtof.h" + +#include "utils/UnitTest/Test.h" + +#include +#include +#include + +// This test is not meant to be left in for final release, it's just here so +// that I can put a value in the input variable and observe the output of strtof +// more easily. +TEST(LlvmLibcStrToFTest, JustPrint) { + const char *input = "0x0.00000000000000000000000000000002"; + char *str_end = nullptr; + + errno = 0; + float result = __llvm_libc::strtof(input, &str_end); + + void *indirection = &result; + unsigned int raw_data = *(unsigned int *)(indirection); + + unsigned int sign = raw_data >> 31; + unsigned int exponent = (raw_data >> 23) & 0xff; + unsigned int mantissa = raw_data & 0x7fffff; + + // EXPECT_FLOAT_EQ(result, (float)(0)); + EXPECT_EQ(str_end - input, ptrdiff_t(0)); + EXPECT_EQ(raw_data, 0u); + EXPECT_EQ(sign, 0u); + EXPECT_EQ(exponent, 0u); + EXPECT_EQ(mantissa, 0u); + EXPECT_EQ(errno, 0); +} + +// This is the set of tests that I have working (verified correct when compared +// to system libc). This is here so I don't break more things when I try to fix +// them. +TEST(LlvmLibcStrToFTest, WorkingExamples) { + char *str_end = nullptr; + float result = 0; + void *indirection = &result; + unsigned int raw_data = *(unsigned int *)(indirection); + unsigned int sign = 0; + unsigned int exponent = 0; + unsigned int mantissa = 0; + + const char *one_in_hex = "0x1"; + errno = 0; + result = __llvm_libc::strtof(one_in_hex, &str_end); + + raw_data = *(unsigned int *)(indirection); + sign = raw_data >> 31; + exponent = (raw_data >> 23) & 0xff; + mantissa = raw_data & 0x7fffff; + + EXPECT_EQ(str_end - one_in_hex, ptrdiff_t(3)); + EXPECT_EQ(raw_data, 0x3f800000u); + EXPECT_EQ(sign, 0u); + EXPECT_EQ(exponent, 0x7fu); + EXPECT_EQ(mantissa, 0u); + EXPECT_EQ(errno, 0); + + const char *sixteen_in_hex = "0x10"; + errno = 0; + result = __llvm_libc::strtof(sixteen_in_hex, &str_end); + + raw_data = *(unsigned int *)(indirection); + sign = raw_data >> 31; + exponent = (raw_data >> 23) & 0xff; + mantissa = raw_data & 0x7fffff; + + EXPECT_EQ(str_end - sixteen_in_hex, ptrdiff_t(4)); + EXPECT_EQ(raw_data, 0x41800000u); + EXPECT_EQ(sign, 0u); + EXPECT_EQ(exponent, 0x83u); + EXPECT_EQ(mantissa, 0u); + EXPECT_EQ(errno, 0); + + const char *seventeen_in_hex = "0x11"; + errno = 0; + result = __llvm_libc::strtof(seventeen_in_hex, &str_end); + + raw_data = *(unsigned int *)(indirection); + sign = raw_data >> 31; + exponent = (raw_data >> 23) & 0xff; + mantissa = raw_data & 0x7fffff; + + EXPECT_EQ(str_end - seventeen_in_hex, ptrdiff_t(4)); + EXPECT_EQ(raw_data, 0x41880000u); + EXPECT_EQ(sign, 0u); + EXPECT_EQ(exponent, 0x83u); + EXPECT_EQ(mantissa, 0x80000u); + EXPECT_EQ(errno, 0); +}