-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[libc]: Implement strfromf() and shared utilities #85438
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
//===-- Implementation header for strfromx() utilitites -------------------===// | ||
// | ||
// 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 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
// According to the C23 standard, any input character sequences except a | ||
// precision specifier and the usual floating point formats, namely | ||
// %{a,A,e,E,f,F,g,G}, are not allowed and any code that does otherwise results | ||
// in undefined behaviour(including use of a '%%' conversion specifier); which | ||
// in this case is that the buffer string is simply populated with the format | ||
// string. The case of the input being NULL should be handled in the calling | ||
// function (strfromf, strfromd, strfroml) itself. | ||
|
||
#ifndef LLVM_LIBC_SRC_STDLIB_STRFROM_UTIL_H | ||
#define LLVM_LIBC_SRC_STDLIB_STRFROM_UTIL_H | ||
|
||
#include "src/__support/CPP/type_traits.h" | ||
#include "src/__support/str_to_integer.h" | ||
#include "src/stdio/printf_core/converter_atlas.h" | ||
#include "src/stdio/printf_core/core_structs.h" | ||
#include "src/stdio/printf_core/writer.h" | ||
|
||
#include <stddef.h> | ||
|
||
namespace LIBC_NAMESPACE::internal { | ||
|
||
template <typename T> | ||
using storage_type = typename fputil::FPBits<T>::StorageType; | ||
|
||
template <typename T> | ||
printf_core::FormatSection parse_format_string(const char *__restrict format, | ||
T fp) { | ||
printf_core::FormatSection section; | ||
size_t cur_pos = 0; | ||
|
||
// There is no typed conversion function to convert single precision float | ||
// to hex exponential format, and the function convert_float_hex_exp() | ||
// requires a double or long double value to work correctly. | ||
// To work around this, we convert fp to double if it is single precision, and | ||
// then use that double precision value in the %{A, a} conversion specifiers. | ||
[[maybe_unused]] double new_fp; | ||
bool t_is_single_prec_type = cpp::is_same<T, float>::value; | ||
if (t_is_single_prec_type) | ||
new_fp = (double)fp; | ||
|
||
if (format[cur_pos] == '%') { | ||
section.has_conv = true; | ||
++cur_pos; | ||
|
||
// handle precision | ||
section.precision = -1; | ||
if (format[cur_pos] == '.') { | ||
++cur_pos; | ||
section.precision = 0; | ||
|
||
// The standard does not allow the '*' (asterisk) operator for strfromx() | ||
// functions | ||
if (internal::isdigit(format[cur_pos])) { | ||
auto result = internal::strtointeger<int>(format + cur_pos, 10); | ||
section.precision += result.value; | ||
cur_pos += result.parsed_len; | ||
} | ||
} | ||
|
||
section.conv_name = format[cur_pos]; | ||
switch (format[cur_pos]) { | ||
case 'a': | ||
case 'A': | ||
if (t_is_single_prec_type) | ||
section.conv_val_raw = cpp::bit_cast<storage_type<double>>(new_fp); | ||
else | ||
section.conv_val_raw = cpp::bit_cast<storage_type<T>>(fp); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if you're just passing the format section to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, but wouldn't I want to do that in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah, that would also work. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok then, should I change this PR from draft to ready for review? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes |
||
break; | ||
case 'e': | ||
case 'E': | ||
case 'f': | ||
case 'F': | ||
case 'g': | ||
case 'G': | ||
section.conv_val_raw = cpp::bit_cast<storage_type<T>>(fp); | ||
break; | ||
default: | ||
section.has_conv = false; | ||
while (format[cur_pos] != '\0') | ||
++cur_pos; | ||
break; | ||
} | ||
|
||
if (format[cur_pos] != '\0') | ||
++cur_pos; | ||
} else { | ||
section.has_conv = false; | ||
// We are looking for exactly one section, so no more '%' | ||
while (format[cur_pos] != '\0') | ||
++cur_pos; | ||
} | ||
|
||
section.raw_string = {format, cur_pos}; | ||
return section; | ||
} | ||
|
||
template <typename T> | ||
int strfromfloat_convert(printf_core::Writer *writer, | ||
const printf_core::FormatSection §ion) { | ||
if (!section.has_conv) | ||
return writer->write(section.raw_string); | ||
|
||
auto res = static_cast<storage_type<T>>(section.conv_val_raw); | ||
|
||
fputil::FPBits<T> strfromfloat_bits(res); | ||
if (strfromfloat_bits.is_inf_or_nan()) | ||
return convert_inf_nan(writer, section); | ||
|
||
switch (section.conv_name) { | ||
case 'f': | ||
case 'F': | ||
return convert_float_decimal_typed(writer, section, strfromfloat_bits); | ||
case 'e': | ||
case 'E': | ||
return convert_float_dec_exp_typed(writer, section, strfromfloat_bits); | ||
case 'a': | ||
case 'A': | ||
return convert_float_hex_exp(writer, section); | ||
case 'g': | ||
case 'G': | ||
return convert_float_dec_auto_typed(writer, section, strfromfloat_bits); | ||
default: | ||
return writer->write(section.raw_string); | ||
} | ||
return -1; | ||
} | ||
|
||
} // namespace LIBC_NAMESPACE::internal | ||
|
||
#endif // LLVM_LIBC_SRC_STDLIB_STRFROM_UTIL_H |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
//===-- Implementation of strfromf ------------------------------*- 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 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
#include "src/stdlib/strfromf.h" | ||
#include "src/stdlib/str_from_util.h" | ||
|
||
#include <stdarg.h> | ||
#include <stddef.h> | ||
|
||
namespace LIBC_NAMESPACE { | ||
|
||
LLVM_LIBC_FUNCTION(int, strfromf, | ||
(char *__restrict s, size_t n, const char *__restrict format, | ||
float fp)) { | ||
LIBC_ASSERT(s != nullptr); | ||
|
||
printf_core::FormatSection section = | ||
internal::parse_format_string(format, fp); | ||
printf_core::WriteBuffer wb(s, (n > 0 ? n - 1 : 0)); | ||
printf_core::Writer writer(&wb); | ||
|
||
int result = 0; | ||
if (section.has_conv) | ||
result = internal::strfromfloat_convert<float>(&writer, section); | ||
else | ||
result = writer.write(section.raw_string); | ||
|
||
if (result < 0) | ||
return result; | ||
|
||
if (n > 0) | ||
wb.buff[wb.buff_cur] = '\0'; | ||
|
||
return writer.get_chars_written(); | ||
} | ||
|
||
} // namespace LIBC_NAMESPACE |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
//===-- Implementation header for strfromf ------------------------*- 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_STRFROMF_H | ||
#define LLVM_LIBC_SRC_STDLIB_STRFROMF_H | ||
|
||
#include <stddef.h> | ||
|
||
namespace LIBC_NAMESPACE { | ||
|
||
int strfromf(char *__restrict s, size_t n, const char *__restrict format, | ||
float fp); | ||
|
||
} // namespace LIBC_NAMESPACE | ||
|
||
#endif // LLVM_LIBC_SRC_STDLIB_STRTOF_H |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
//===-- Unittests for strfromf --------------------------------------------===// | ||
// | ||
// 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/strfromf.h" | ||
#include "test/UnitTest/Test.h" | ||
|
||
TEST(LlvmLibcStrfromfTest, DecimalFloatFormat) { | ||
char buff[100]; | ||
int written; | ||
|
||
written = LIBC_NAMESPACE::strfromf(buff, 16, "%f", 1.0); | ||
EXPECT_EQ(written, 8); | ||
ASSERT_STREQ(buff, "1.000000"); | ||
|
||
written = LIBC_NAMESPACE::strfromf(buff, 20, "%f", 1234567890.0); | ||
EXPECT_EQ(written, 17); | ||
ASSERT_STREQ(buff, "1234567936.000000"); | ||
|
||
written = LIBC_NAMESPACE::strfromf(buff, 5, "%f", 1234567890.0); | ||
EXPECT_EQ(written, 17); | ||
ASSERT_STREQ(buff, "1234"); | ||
|
||
written = LIBC_NAMESPACE::strfromf(buff, 67, "%.3f", 1.0); | ||
EXPECT_EQ(written, 5); | ||
ASSERT_STREQ(buff, "1.000"); | ||
|
||
written = LIBC_NAMESPACE::strfromf(buff, 20, "%1f", 1234567890.0); | ||
EXPECT_EQ(written, 3); | ||
ASSERT_STREQ(buff, "%1f"); | ||
} | ||
|
||
TEST(LlvmLibcStrfromfTest, HexExpFloatFormat) { | ||
char buff[100]; | ||
int written; | ||
|
||
written = LIBC_NAMESPACE::strfromf(buff, 0, "%a", 1234567890.0); | ||
EXPECT_EQ(written, 14); | ||
|
||
written = LIBC_NAMESPACE::strfromf(buff, 20, "%a", 1234567890.0); | ||
EXPECT_EQ(written, 14); | ||
ASSERT_STREQ(buff, "0x1.26580cp+30"); | ||
|
||
written = LIBC_NAMESPACE::strfromf(buff, 20, "%A", 1234567890.0); | ||
EXPECT_EQ(written, 14); | ||
ASSERT_STREQ(buff, "0X1.26580CP+30"); | ||
} | ||
|
||
TEST(LlvmLibcStrfromfTest, DecimalExpFloatFormat) { | ||
char buff[100]; | ||
int written; | ||
written = LIBC_NAMESPACE::strfromf(buff, 20, "%.9e", 1234567890.0); | ||
EXPECT_EQ(written, 15); | ||
ASSERT_STREQ(buff, "1.234567936e+09"); | ||
|
||
written = LIBC_NAMESPACE::strfromf(buff, 20, "%.9E", 1234567890.0); | ||
EXPECT_EQ(written, 15); | ||
ASSERT_STREQ(buff, "1.234567936E+09"); | ||
} | ||
|
||
TEST(LlvmLibcStrfromfTest, AutoDecimalFloatFormat) { | ||
char buff[100]; | ||
int written; | ||
|
||
written = LIBC_NAMESPACE::strfromf(buff, 20, "%.9g", 1234567890.0); | ||
EXPECT_EQ(written, 14); | ||
ASSERT_STREQ(buff, "1.23456794e+09"); | ||
|
||
written = LIBC_NAMESPACE::strfromf(buff, 20, "%.9G", 1234567890.0); | ||
EXPECT_EQ(written, 14); | ||
ASSERT_STREQ(buff, "1.23456794E+09"); | ||
|
||
written = LIBC_NAMESPACE::strfromf(buff, 0, "%G", 1.0); | ||
EXPECT_EQ(written, 1); | ||
} | ||
|
||
TEST(LlvmLibcStrfromfTest, ImproperFormatString) { | ||
|
||
char buff[100]; | ||
int retval; | ||
retval = LIBC_NAMESPACE::strfromf( | ||
buff, 37, "A simple string with no conversions.", 1.0); | ||
EXPECT_EQ(retval, 36); | ||
ASSERT_STREQ(buff, "A simple string with no conversions."); | ||
|
||
retval = LIBC_NAMESPACE::strfromf( | ||
buff, 37, "%A simple string with one conversion, should overwrite.", 1.0); | ||
EXPECT_EQ(retval, 6); | ||
ASSERT_STREQ(buff, "0X1P+0"); | ||
|
||
retval = LIBC_NAMESPACE::strfromf(buff, 74, | ||
"A simple string with one conversion in %A " | ||
"between, writes string as it is", | ||
1.0); | ||
EXPECT_EQ(retval, 73); | ||
ASSERT_STREQ(buff, "A simple string with one conversion in %A between, " | ||
"writes string as it is"); | ||
|
||
retval = LIBC_NAMESPACE::strfromf(buff, 36, | ||
"A simple string with one conversion", 1.0); | ||
EXPECT_EQ(retval, 35); | ||
ASSERT_STREQ(buff, "A simple string with one conversion"); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add a comment explaining why this is necessary