Skip to content

Commit 5ea1520

Browse files
authored
[libc]: Implement strfromf() and shared utilities (#85438)
Fixes #84244. Implements the function `strfromf()` introduced in C23, and adds shared utilities for implementation of other `strfrom*()` functions, including `strfromd()` and `strfroml()`.
1 parent 10b0e35 commit 5ea1520

File tree

8 files changed

+343
-1
lines changed

8 files changed

+343
-1
lines changed

libc/config/linux/x86_64/entrypoints.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ set(TARGET_LIBC_ENTRYPOINTS
180180
libc.src.stdlib.qsort_r
181181
libc.src.stdlib.rand
182182
libc.src.stdlib.srand
183+
libc.src.stdlib.strfromf
183184
libc.src.stdlib.strtod
184185
libc.src.stdlib.strtof
185186
libc.src.stdlib.strtol

libc/spec/stdc.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -956,6 +956,8 @@ def StdC : StandardSpec<"stdc"> {
956956
FunctionSpec<"rand", RetValSpec<IntType>, [ArgSpec<VoidType>]>,
957957
FunctionSpec<"srand", RetValSpec<VoidType>, [ArgSpec<UnsignedIntType>]>,
958958

959+
FunctionSpec<"strfromf", RetValSpec<IntType>, [ArgSpec<CharRestrictedPtr>, ArgSpec<SizeTType>, ArgSpec<ConstCharRestrictedPtr>, ArgSpec<FloatType>]>,
960+
959961
FunctionSpec<"strtof", RetValSpec<FloatType>, [ArgSpec<ConstCharRestrictedPtr>, ArgSpec<CharRestrictedPtrPtr>]>,
960962
FunctionSpec<"strtod", RetValSpec<DoubleType>, [ArgSpec<ConstCharRestrictedPtr>, ArgSpec<CharRestrictedPtrPtr>]>,
961963
FunctionSpec<"strtold", RetValSpec<LongDoubleType>, [ArgSpec<ConstCharRestrictedPtr>, ArgSpec<CharRestrictedPtrPtr>]>,

libc/src/stdlib/CMakeLists.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,28 @@ add_entrypoint_object(
5252
libc.config.linux.app_h
5353
)
5454

55+
add_entrypoint_object(
56+
strfromf
57+
SRCS
58+
strfromf.cpp
59+
HDRS
60+
strfromf.h
61+
DEPENDS
62+
.str_from_util
63+
)
64+
65+
add_header_library(
66+
str_from_util
67+
HDRS
68+
str_from_util.h
69+
DEPENDS
70+
libc.src.stdio.printf_core.converter
71+
libc.src.stdio.printf_core.core_structs
72+
libc.src.stdio.printf_core.writer
73+
libc.src.__support.str_to_integer
74+
libc.src.__support.CPP.type_traits
75+
)
76+
5577
add_entrypoint_object(
5678
strtof
5779
SRCS

libc/src/stdlib/str_from_util.h

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
//===-- Implementation header for strfromx() utilitites -------------------===//
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+
// According to the C23 standard, any input character sequences except a
10+
// precision specifier and the usual floating point formats, namely
11+
// %{a,A,e,E,f,F,g,G}, are not allowed and any code that does otherwise results
12+
// in undefined behaviour(including use of a '%%' conversion specifier); which
13+
// in this case is that the buffer string is simply populated with the format
14+
// string. The case of the input being NULL should be handled in the calling
15+
// function (strfromf, strfromd, strfroml) itself.
16+
17+
#ifndef LLVM_LIBC_SRC_STDLIB_STRFROM_UTIL_H
18+
#define LLVM_LIBC_SRC_STDLIB_STRFROM_UTIL_H
19+
20+
#include "src/__support/CPP/type_traits.h"
21+
#include "src/__support/str_to_integer.h"
22+
#include "src/stdio/printf_core/converter_atlas.h"
23+
#include "src/stdio/printf_core/core_structs.h"
24+
#include "src/stdio/printf_core/writer.h"
25+
26+
#include <stddef.h>
27+
28+
namespace LIBC_NAMESPACE::internal {
29+
30+
template <typename T>
31+
using storage_type = typename fputil::FPBits<T>::StorageType;
32+
33+
template <typename T>
34+
printf_core::FormatSection parse_format_string(const char *__restrict format,
35+
T fp) {
36+
printf_core::FormatSection section;
37+
size_t cur_pos = 0;
38+
39+
// There is no typed conversion function to convert single precision float
40+
// to hex exponential format, and the function convert_float_hex_exp()
41+
// requires a double or long double value to work correctly.
42+
// To work around this, we convert fp to double if it is single precision, and
43+
// then use that double precision value in the %{A, a} conversion specifiers.
44+
[[maybe_unused]] double new_fp;
45+
bool t_is_single_prec_type = cpp::is_same<T, float>::value;
46+
if (t_is_single_prec_type)
47+
new_fp = (double)fp;
48+
49+
if (format[cur_pos] == '%') {
50+
section.has_conv = true;
51+
++cur_pos;
52+
53+
// handle precision
54+
section.precision = -1;
55+
if (format[cur_pos] == '.') {
56+
++cur_pos;
57+
section.precision = 0;
58+
59+
// The standard does not allow the '*' (asterisk) operator for strfromx()
60+
// functions
61+
if (internal::isdigit(format[cur_pos])) {
62+
auto result = internal::strtointeger<int>(format + cur_pos, 10);
63+
section.precision += result.value;
64+
cur_pos += result.parsed_len;
65+
}
66+
}
67+
68+
section.conv_name = format[cur_pos];
69+
switch (format[cur_pos]) {
70+
case 'a':
71+
case 'A':
72+
if (t_is_single_prec_type)
73+
section.conv_val_raw = cpp::bit_cast<storage_type<double>>(new_fp);
74+
else
75+
section.conv_val_raw = cpp::bit_cast<storage_type<T>>(fp);
76+
break;
77+
case 'e':
78+
case 'E':
79+
case 'f':
80+
case 'F':
81+
case 'g':
82+
case 'G':
83+
section.conv_val_raw = cpp::bit_cast<storage_type<T>>(fp);
84+
break;
85+
default:
86+
section.has_conv = false;
87+
while (format[cur_pos] != '\0')
88+
++cur_pos;
89+
break;
90+
}
91+
92+
if (format[cur_pos] != '\0')
93+
++cur_pos;
94+
} else {
95+
section.has_conv = false;
96+
// We are looking for exactly one section, so no more '%'
97+
while (format[cur_pos] != '\0')
98+
++cur_pos;
99+
}
100+
101+
section.raw_string = {format, cur_pos};
102+
return section;
103+
}
104+
105+
template <typename T>
106+
int strfromfloat_convert(printf_core::Writer *writer,
107+
const printf_core::FormatSection &section) {
108+
if (!section.has_conv)
109+
return writer->write(section.raw_string);
110+
111+
auto res = static_cast<storage_type<T>>(section.conv_val_raw);
112+
113+
fputil::FPBits<T> strfromfloat_bits(res);
114+
if (strfromfloat_bits.is_inf_or_nan())
115+
return convert_inf_nan(writer, section);
116+
117+
switch (section.conv_name) {
118+
case 'f':
119+
case 'F':
120+
return convert_float_decimal_typed(writer, section, strfromfloat_bits);
121+
case 'e':
122+
case 'E':
123+
return convert_float_dec_exp_typed(writer, section, strfromfloat_bits);
124+
case 'a':
125+
case 'A':
126+
return convert_float_hex_exp(writer, section);
127+
case 'g':
128+
case 'G':
129+
return convert_float_dec_auto_typed(writer, section, strfromfloat_bits);
130+
default:
131+
return writer->write(section.raw_string);
132+
}
133+
return -1;
134+
}
135+
136+
} // namespace LIBC_NAMESPACE::internal
137+
138+
#endif // LLVM_LIBC_SRC_STDLIB_STRFROM_UTIL_H

libc/src/stdlib/strfromf.cpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//===-- Implementation of strfromf ------------------------------*- 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+
#include "src/stdlib/strfromf.h"
10+
#include "src/stdlib/str_from_util.h"
11+
12+
#include <stdarg.h>
13+
#include <stddef.h>
14+
15+
namespace LIBC_NAMESPACE {
16+
17+
LLVM_LIBC_FUNCTION(int, strfromf,
18+
(char *__restrict s, size_t n, const char *__restrict format,
19+
float fp)) {
20+
LIBC_ASSERT(s != nullptr);
21+
22+
printf_core::FormatSection section =
23+
internal::parse_format_string(format, fp);
24+
printf_core::WriteBuffer wb(s, (n > 0 ? n - 1 : 0));
25+
printf_core::Writer writer(&wb);
26+
27+
int result = 0;
28+
if (section.has_conv)
29+
result = internal::strfromfloat_convert<float>(&writer, section);
30+
else
31+
result = writer.write(section.raw_string);
32+
33+
if (result < 0)
34+
return result;
35+
36+
if (n > 0)
37+
wb.buff[wb.buff_cur] = '\0';
38+
39+
return writer.get_chars_written();
40+
}
41+
42+
} // namespace LIBC_NAMESPACE

libc/src/stdlib/strfromf.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//===-- Implementation header for strfromf ------------------------*- 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_SRC_STDLIB_STRFROMF_H
10+
#define LLVM_LIBC_SRC_STDLIB_STRFROMF_H
11+
12+
#include <stddef.h>
13+
14+
namespace LIBC_NAMESPACE {
15+
16+
int strfromf(char *__restrict s, size_t n, const char *__restrict format,
17+
float fp);
18+
19+
} // namespace LIBC_NAMESPACE
20+
21+
#endif // LLVM_LIBC_SRC_STDLIB_STRTOF_H

libc/test/src/stdlib/CMakeLists.txt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,16 @@ add_libc_test(
168168
.strtol_test_support
169169
)
170170

171+
add_libc_test(
172+
strfromf_test
173+
SUITE
174+
libc-stdlib-tests
175+
SRCS
176+
strfromf_test.cpp
177+
DEPENDS
178+
libc.src.stdlib.strfromf
179+
)
180+
171181
add_libc_test(
172182
abs_test
173183
SUITE
@@ -259,7 +269,6 @@ add_libc_test(
259269
libc.src.stdlib.qsort
260270
)
261271

262-
263272
add_libc_test(
264273
qsort_r_test
265274
SUITE
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
//===-- Unittests for strfromf --------------------------------------------===//
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+
#include "src/stdlib/strfromf.h"
10+
#include "test/UnitTest/Test.h"
11+
12+
TEST(LlvmLibcStrfromfTest, DecimalFloatFormat) {
13+
char buff[100];
14+
int written;
15+
16+
written = LIBC_NAMESPACE::strfromf(buff, 16, "%f", 1.0);
17+
EXPECT_EQ(written, 8);
18+
ASSERT_STREQ(buff, "1.000000");
19+
20+
written = LIBC_NAMESPACE::strfromf(buff, 20, "%f", 1234567890.0);
21+
EXPECT_EQ(written, 17);
22+
ASSERT_STREQ(buff, "1234567936.000000");
23+
24+
written = LIBC_NAMESPACE::strfromf(buff, 5, "%f", 1234567890.0);
25+
EXPECT_EQ(written, 17);
26+
ASSERT_STREQ(buff, "1234");
27+
28+
written = LIBC_NAMESPACE::strfromf(buff, 67, "%.3f", 1.0);
29+
EXPECT_EQ(written, 5);
30+
ASSERT_STREQ(buff, "1.000");
31+
32+
written = LIBC_NAMESPACE::strfromf(buff, 20, "%1f", 1234567890.0);
33+
EXPECT_EQ(written, 3);
34+
ASSERT_STREQ(buff, "%1f");
35+
}
36+
37+
TEST(LlvmLibcStrfromfTest, HexExpFloatFormat) {
38+
char buff[100];
39+
int written;
40+
41+
written = LIBC_NAMESPACE::strfromf(buff, 0, "%a", 1234567890.0);
42+
EXPECT_EQ(written, 14);
43+
44+
written = LIBC_NAMESPACE::strfromf(buff, 20, "%a", 1234567890.0);
45+
EXPECT_EQ(written, 14);
46+
ASSERT_STREQ(buff, "0x1.26580cp+30");
47+
48+
written = LIBC_NAMESPACE::strfromf(buff, 20, "%A", 1234567890.0);
49+
EXPECT_EQ(written, 14);
50+
ASSERT_STREQ(buff, "0X1.26580CP+30");
51+
}
52+
53+
TEST(LlvmLibcStrfromfTest, DecimalExpFloatFormat) {
54+
char buff[100];
55+
int written;
56+
written = LIBC_NAMESPACE::strfromf(buff, 20, "%.9e", 1234567890.0);
57+
EXPECT_EQ(written, 15);
58+
ASSERT_STREQ(buff, "1.234567936e+09");
59+
60+
written = LIBC_NAMESPACE::strfromf(buff, 20, "%.9E", 1234567890.0);
61+
EXPECT_EQ(written, 15);
62+
ASSERT_STREQ(buff, "1.234567936E+09");
63+
}
64+
65+
TEST(LlvmLibcStrfromfTest, AutoDecimalFloatFormat) {
66+
char buff[100];
67+
int written;
68+
69+
written = LIBC_NAMESPACE::strfromf(buff, 20, "%.9g", 1234567890.0);
70+
EXPECT_EQ(written, 14);
71+
ASSERT_STREQ(buff, "1.23456794e+09");
72+
73+
written = LIBC_NAMESPACE::strfromf(buff, 20, "%.9G", 1234567890.0);
74+
EXPECT_EQ(written, 14);
75+
ASSERT_STREQ(buff, "1.23456794E+09");
76+
77+
written = LIBC_NAMESPACE::strfromf(buff, 0, "%G", 1.0);
78+
EXPECT_EQ(written, 1);
79+
}
80+
81+
TEST(LlvmLibcStrfromfTest, ImproperFormatString) {
82+
83+
char buff[100];
84+
int retval;
85+
retval = LIBC_NAMESPACE::strfromf(
86+
buff, 37, "A simple string with no conversions.", 1.0);
87+
EXPECT_EQ(retval, 36);
88+
ASSERT_STREQ(buff, "A simple string with no conversions.");
89+
90+
retval = LIBC_NAMESPACE::strfromf(
91+
buff, 37, "%A simple string with one conversion, should overwrite.", 1.0);
92+
EXPECT_EQ(retval, 6);
93+
ASSERT_STREQ(buff, "0X1P+0");
94+
95+
retval = LIBC_NAMESPACE::strfromf(buff, 74,
96+
"A simple string with one conversion in %A "
97+
"between, writes string as it is",
98+
1.0);
99+
EXPECT_EQ(retval, 73);
100+
ASSERT_STREQ(buff, "A simple string with one conversion in %A between, "
101+
"writes string as it is");
102+
103+
retval = LIBC_NAMESPACE::strfromf(buff, 36,
104+
"A simple string with one conversion", 1.0);
105+
EXPECT_EQ(retval, 35);
106+
ASSERT_STREQ(buff, "A simple string with one conversion");
107+
}

0 commit comments

Comments
 (0)