Skip to content

Commit 08de46e

Browse files
[libcxx][libc] Hand in Hand PoC with from_chars
DO NOT MERGE, PROOF OF CONCEPT ONLY. This patch aims to demonstrate the utility of sharing code between libc and libc++ by using the libc float conversion code in the libc++ function from_chars. This patch adds from_chars for float and double (long double is possible but was causing errors so was skipped here), as well as a test to demonstrate that it works. This is very much just a proof of concept, not intended to be committed as-is. The from_chars code written is copied from the libc parsing code and is not functionally complete, nor does it follow the correct coding style.
1 parent eb03279 commit 08de46e

File tree

9 files changed

+326
-2
lines changed

9 files changed

+326
-2
lines changed

libc/shared/str_to_float.h

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//===-- String to float conversion utils ------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_LIBC_SHARED_STR_TO_FLOAT_H
10+
#define LLVM_LIBC_SHARED_STR_TO_FLOAT_H
11+
12+
#include "src/__support/str_to_float.h"
13+
14+
namespace LIBC_NAMESPACE::shared {
15+
16+
// WARNING: This is a proof of concept. In future the interface point for libcxx
17+
// won't be using libc internal classes.
18+
19+
template <class T>
20+
inline internal::FloatConvertReturn<T> decimal_exp_to_float(
21+
internal::ExpandedFloat<T> init_num, bool truncated,
22+
internal::RoundDirection round, const char *__restrict num_start,
23+
const size_t num_len = cpp::numeric_limits<size_t>::max()) {
24+
return internal::decimal_exp_to_float(init_num, truncated, round, num_start,
25+
num_len);
26+
}
27+
} // namespace LIBC_NAMESPACE::shared
28+
29+
#endif // LLVM_LIBC_SHARED_STR_TO_FLOAT_H

libcxx/include/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ set(files
235235
__bit/rotate.h
236236
__bit_reference
237237
__charconv/chars_format.h
238+
__charconv/from_chars_floating_point.h
238239
__charconv/from_chars_integral.h
239240
__charconv/from_chars_result.h
240241
__charconv/tables.h
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// -*- C++ -*-
2+
//===----------------------------------------------------------------------===//
3+
//
4+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5+
// See https://llvm.org/LICENSE.txt for license information.
6+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7+
//
8+
//===----------------------------------------------------------------------===//
9+
10+
#ifndef _LIBCPP___CHARCONV_FROM_CHARS_FLOATING_POINT_H
11+
#define _LIBCPP___CHARCONV_FROM_CHARS_FLOATING_POINT_H
12+
13+
#include <__assert>
14+
#include <__charconv/chars_format.h>
15+
#include <__charconv/from_chars_result.h>
16+
#include <__charconv/traits.h>
17+
#include <__config>
18+
#include <__system_error/errc.h>
19+
#include <__type_traits/enable_if.h>
20+
#include <__type_traits/integral_constant.h>
21+
#include <__type_traits/is_floating_point.h>
22+
23+
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
24+
# pragma GCC system_header
25+
#endif
26+
27+
_LIBCPP_PUSH_MACROS
28+
#include <__undef_macros>
29+
30+
_LIBCPP_BEGIN_NAMESPACE_STD
31+
32+
#if _LIBCPP_STD_VER >= 17
33+
34+
from_chars_result from_chars_floating_point(
35+
const char* __first, const char* __last, float& value, chars_format fmt = chars_format::general);
36+
37+
from_chars_result from_chars_floating_point(
38+
const char* __first, const char* __last, double& value, chars_format fmt = chars_format::general);
39+
40+
// template <typename _Tp, __enable_if_t<is_floating_point<_Tp>::value, int> = 0>
41+
// inline from_chars_result
42+
// from_chars(const char* __first, const char* __last, _Tp& __value, chars_format fmt = chars_format::general) {
43+
// return std::from_chars_floating_point(__first, __last, __value, fmt);
44+
// }
45+
46+
// inline from_chars_result
47+
// from_chars(const char* __first, const char* __last, float& __value, chars_format fmt = chars_format::general) {
48+
// return std::from_chars_floating_point(__first, __last, __value, fmt);
49+
// }
50+
51+
// inline from_chars_result
52+
// from_chars(const char* __first, const char* __last, double& __value, chars_format fmt = chars_format::general) {
53+
// return std::from_chars_floating_point(__first, __last, __value, fmt);
54+
// }
55+
56+
from_chars_result
57+
from_chars(const char* __first, const char* __last, float& __value, chars_format fmt = chars_format::general);
58+
59+
from_chars_result
60+
from_chars(const char* __first, const char* __last, double& __value, chars_format fmt = chars_format::general);
61+
62+
#endif // _LIBCPP_STD_VER >= 17
63+
64+
_LIBCPP_END_NAMESPACE_STD
65+
66+
_LIBCPP_POP_MACROS
67+
68+
#endif // _LIBCPP___CHARCONV_FROM_CHARS_FLOATING_POINT_H

libcxx/include/charconv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ namespace std {
7373

7474
#if _LIBCPP_STD_VER >= 17
7575
# include <__charconv/chars_format.h>
76+
# include <__charconv/from_chars_floating_point.h>
7677
# include <__charconv/from_chars_integral.h>
7778
# include <__charconv/from_chars_result.h>
7879
# include <__charconv/tables.h>

libcxx/src/CMakeLists.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ set(LIBCXX_SOURCES
3131
include/ryu/f2s.h
3232
include/ryu/ryu.h
3333
include/to_chars_floating_point.h
34+
include/from_chars_floating_point.h
3435
legacy_pointer_safety.cpp
3536
memory.cpp
3637
memory_resource.cpp
@@ -179,7 +180,7 @@ split_list(LIBCXX_LINK_FLAGS)
179180
# Build the shared library.
180181
if (LIBCXX_ENABLE_SHARED)
181182
add_library(cxx_shared SHARED ${exclude_from_all} ${LIBCXX_SOURCES} ${LIBCXX_HEADERS})
182-
target_include_directories(cxx_shared PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
183+
target_include_directories(cxx_shared PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/../../libc) #TODO: Do this properly
183184
target_link_libraries(cxx_shared PUBLIC cxx-headers libcxx-libc-shared
184185
PRIVATE ${LIBCXX_LIBRARIES})
185186
set_target_properties(cxx_shared
@@ -272,7 +273,7 @@ set(CMAKE_STATIC_LIBRARY_PREFIX "lib")
272273
# Build the static library.
273274
if (LIBCXX_ENABLE_STATIC)
274275
add_library(cxx_static STATIC ${exclude_from_all} ${LIBCXX_SOURCES} ${LIBCXX_HEADERS})
275-
target_include_directories(cxx_static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
276+
target_include_directories(cxx_static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/../../libc) #TODO: Do this properly
276277
target_link_libraries(cxx_static PUBLIC cxx-headers libcxx-libc-static
277278
PRIVATE ${LIBCXX_LIBRARIES}
278279
PRIVATE libcxx-abi-static)

libcxx/src/charconv.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <charconv>
1010
#include <string.h>
1111

12+
#include "include/from_chars_floating_point.h"
1213
#include "include/to_chars_floating_point.h"
1314

1415
_LIBCPP_BEGIN_NAMESPACE_STD
@@ -74,4 +75,20 @@ to_chars_result to_chars(char* __first, char* __last, long double __value, chars
7475
__first, __last, static_cast<double>(__value), __fmt, __precision);
7576
}
7677

78+
from_chars_result from_chars_floating_point(const char* __first, const char* __last, float& value, chars_format fmt) {
79+
return from_chars_floating_point<float>(__first, __last, value, fmt);
80+
}
81+
82+
from_chars_result from_chars_floating_point(const char* __first, const char* __last, double& value, chars_format fmt) {
83+
return from_chars_floating_point<double>(__first, __last, value, fmt);
84+
}
85+
86+
from_chars_result from_chars(const char* __first, const char* __last, float& __value, chars_format fmt) {
87+
return std::from_chars_floating_point(__first, __last, __value, fmt);
88+
}
89+
90+
from_chars_result from_chars(const char* __first, const char* __last, double& __value, chars_format fmt) {
91+
return std::from_chars_floating_point(__first, __last, __value, fmt);
92+
}
93+
7794
_LIBCPP_END_NAMESPACE_STD
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef _LIBCPP_SRC_INCLUDE_FROM_CHARS_FLOATING_POINT_H
10+
#define _LIBCPP_SRC_INCLUDE_FROM_CHARS_FLOATING_POINT_H
11+
12+
// NEVER DO THIS FOR REAL, this is just for demonstration purposes.
13+
#define LIBC_NAMESPACE libc_namespace_in_libcxx
14+
15+
// This header is in the shared LLVM-libc header library.
16+
#include "shared/str_to_float.h"
17+
18+
#include <__assert>
19+
#include <__config>
20+
#include <charconv>
21+
#include <limits>
22+
#include <type_traits>
23+
24+
// Included for the _Floating_type_traits class
25+
#include "to_chars_floating_point.h"
26+
27+
_LIBCPP_BEGIN_NAMESPACE_STD
28+
29+
template <typename _Tp, __enable_if_t<std::is_floating_point<_Tp>::value, int> = 0>
30+
from_chars_result from_chars_floating_point(const char* __first, const char* __last, _Tp& value, chars_format fmt) {
31+
using _Traits = _Floating_type_traits<_Tp>;
32+
using _Uint_type = typename _Traits::_Uint_type;
33+
ptrdiff_t length = __last - __first;
34+
_LIBCPP_ASSERT_INTERNAL(length > 0, "");
35+
36+
// hacky parsing code as example. Not intended for actual use. I'm just going to handle the base 10
37+
// chars_format::general case. Also, no sign, inf, or nan handling.
38+
_LIBCPP_ASSERT_INTERNAL(fmt == std::chars_format::general, "");
39+
40+
const char* src = __first; // rename to match the libc code copied for this section.
41+
42+
_Uint_type mantissa = 0;
43+
int exponent = 0;
44+
bool truncated = false;
45+
bool seen_digit = false;
46+
bool after_decimal = false;
47+
size_t index = 0;
48+
const size_t BASE = 10;
49+
constexpr char EXPONENT_MARKER = 'e';
50+
constexpr char DECIMAL_POINT = '.';
51+
52+
// The loop fills the mantissa with as many digits as it can hold
53+
const _Uint_type bitstype_max_div_by_base = numeric_limits<_Uint_type>::max() / BASE;
54+
while (index < length) {
55+
if (LIBC_NAMESPACE::internal::isdigit(src[index])) {
56+
uint32_t digit = src[index] - '0';
57+
seen_digit = true;
58+
59+
if (mantissa < bitstype_max_div_by_base) {
60+
mantissa = (mantissa * BASE) + digit;
61+
if (after_decimal) {
62+
--exponent;
63+
}
64+
} else {
65+
if (digit > 0)
66+
truncated = true;
67+
if (!after_decimal)
68+
++exponent;
69+
}
70+
71+
++index;
72+
continue;
73+
}
74+
if (src[index] == DECIMAL_POINT) {
75+
if (after_decimal) {
76+
break; // this means that src[index] points to a second decimal point, ending the number.
77+
}
78+
after_decimal = true;
79+
++index;
80+
continue;
81+
}
82+
// The character is neither a digit nor a decimal point.
83+
break;
84+
}
85+
86+
if (!seen_digit)
87+
return {src + index, {}};
88+
89+
if (index < length && LIBC_NAMESPACE::internal::tolower(src[index]) == EXPONENT_MARKER) {
90+
bool has_sign = false;
91+
if (index + 1 < length && (src[index + 1] == '+' || src[index + 1] == '-')) {
92+
has_sign = true;
93+
}
94+
if (index + 1 + static_cast<size_t>(has_sign) < length &&
95+
LIBC_NAMESPACE::internal::isdigit(src[index + 1 + static_cast<size_t>(has_sign)])) {
96+
++index;
97+
auto result = LIBC_NAMESPACE::internal::strtointeger<int32_t>(src + index, 10);
98+
// if (result.has_error())
99+
// output.error = result.error;
100+
int32_t add_to_exponent = result.value;
101+
index += result.parsed_len;
102+
103+
// Here we do this operation as int64 to avoid overflow.
104+
int64_t temp_exponent = static_cast<int64_t>(exponent) + static_cast<int64_t>(add_to_exponent);
105+
106+
// If the result is in the valid range, then we use it. The valid range is
107+
// also within the int32 range, so this prevents overflow issues.
108+
if (temp_exponent > LIBC_NAMESPACE::fputil::FPBits<_Tp>::MAX_BIASED_EXPONENT) {
109+
exponent = LIBC_NAMESPACE::fputil::FPBits<_Tp>::MAX_BIASED_EXPONENT;
110+
} else if (temp_exponent < -LIBC_NAMESPACE::fputil::FPBits<_Tp>::MAX_BIASED_EXPONENT) {
111+
exponent = -LIBC_NAMESPACE::fputil::FPBits<_Tp>::MAX_BIASED_EXPONENT;
112+
} else {
113+
exponent = static_cast<int32_t>(temp_exponent);
114+
}
115+
}
116+
}
117+
118+
LIBC_NAMESPACE::internal::ExpandedFloat<_Tp> expanded_float = {0, 0};
119+
if (mantissa != 0) {
120+
auto temp = LIBC_NAMESPACE::shared::decimal_exp_to_float<_Tp>(
121+
{mantissa, exponent}, truncated, LIBC_NAMESPACE::internal::RoundDirection::Nearest, src, length);
122+
expanded_float = temp.num;
123+
// Note: there's also an error value in temp.error. I'm not doing that error handling right now though.
124+
}
125+
126+
auto result = LIBC_NAMESPACE::fputil::FPBits<_Tp>();
127+
result.set_mantissa(expanded_float.mantissa);
128+
result.set_biased_exponent(expanded_float.exponent);
129+
value = result.get_val();
130+
return {src + index, {}};
131+
}
132+
133+
_LIBCPP_END_NAMESPACE_STD
134+
135+
#endif //_LIBCPP_SRC_INCLUDE_FROM_CHARS_FLOATING_POINT_H
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// UNSUPPORTED: c++03, c++11, c++14
10+
11+
// <charconv>
12+
13+
// constexpr from_chars_result from_chars(const char* first, const char* last,
14+
// Float& value, chars_format fmt = chars_format::general)
15+
16+
#include <charconv>
17+
#include "test_macros.h"
18+
#include "charconv_test_helpers.h"
19+
20+
template <typename T>
21+
struct test_basics {
22+
TEST_CONSTEXPR_CXX23 void operator()() {
23+
std::from_chars_result r;
24+
T x;
25+
26+
{
27+
char s[] = "001x";
28+
29+
// the expected form of the subject sequence is a nonempty sequence of
30+
// decimal digits optionally containing a decimal-point character, then
31+
// an optional exponent part as defined in 6.4.4.3, excluding any digit
32+
// separators (6.4.4.2); (C23 7.24.1.5)
33+
r = std::from_chars(s, s + sizeof(s), x);
34+
assert(r.ec == std::errc{});
35+
assert(r.ptr == s + 3);
36+
assert(x == T(1.0));
37+
}
38+
39+
{
40+
char s[] = "1.5e10";
41+
42+
r = std::from_chars(s, s + sizeof(s), x);
43+
assert(r.ec == std::errc{});
44+
assert(r.ptr == s + 6);
45+
assert(x == T(1.5e10));
46+
}
47+
48+
{
49+
char s[] = "20040229";
50+
51+
// This number is halfway between two float values.
52+
r = std::from_chars(s, s + sizeof(s), x);
53+
assert(r.ec == std::errc{});
54+
assert(r.ptr == s + 8);
55+
assert(x == T(20040229));
56+
}
57+
}
58+
};
59+
60+
TEST_CONSTEXPR_CXX23 bool test() {
61+
run<test_basics>(all_floats);
62+
63+
return true;
64+
}
65+
66+
int main(int, char**) {
67+
test();
68+
69+
return 0;
70+
}

libcxx/test/support/charconv_test_helpers.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,8 @@ auto all_unsigned = type_list<
317317
>();
318318
auto integrals = concat(all_signed, all_unsigned);
319319

320+
auto all_floats = type_list< float, double >(); //TODO: Add long double
321+
320322
template <template <typename> class Fn, typename... Ts>
321323
TEST_CONSTEXPR_CXX23 void
322324
run(type_list<Ts...>)

0 commit comments

Comments
 (0)