Skip to content

[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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libc/config/linux/x86_64/entrypoints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ set(TARGET_LIBC_ENTRYPOINTS
libc.src.stdlib.qsort_r
libc.src.stdlib.rand
libc.src.stdlib.srand
libc.src.stdlib.strfromf
libc.src.stdlib.strtod
libc.src.stdlib.strtof
libc.src.stdlib.strtol
Expand Down
2 changes: 2 additions & 0 deletions libc/spec/stdc.td
Original file line number Diff line number Diff line change
Expand Up @@ -956,6 +956,8 @@ def StdC : StandardSpec<"stdc"> {
FunctionSpec<"rand", RetValSpec<IntType>, [ArgSpec<VoidType>]>,
FunctionSpec<"srand", RetValSpec<VoidType>, [ArgSpec<UnsignedIntType>]>,

FunctionSpec<"strfromf", RetValSpec<IntType>, [ArgSpec<CharRestrictedPtr>, ArgSpec<SizeTType>, ArgSpec<ConstCharRestrictedPtr>, ArgSpec<FloatType>]>,

FunctionSpec<"strtof", RetValSpec<FloatType>, [ArgSpec<ConstCharRestrictedPtr>, ArgSpec<CharRestrictedPtrPtr>]>,
FunctionSpec<"strtod", RetValSpec<DoubleType>, [ArgSpec<ConstCharRestrictedPtr>, ArgSpec<CharRestrictedPtrPtr>]>,
FunctionSpec<"strtold", RetValSpec<LongDoubleType>, [ArgSpec<ConstCharRestrictedPtr>, ArgSpec<CharRestrictedPtrPtr>]>,
Expand Down
22 changes: 22 additions & 0 deletions libc/src/stdlib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,28 @@ add_entrypoint_object(
libc.config.linux.app_h
)

add_entrypoint_object(
strfromf
SRCS
strfromf.cpp
HDRS
strfromf.h
DEPENDS
.str_from_util
)

add_header_library(
str_from_util
HDRS
str_from_util.h
DEPENDS
libc.src.stdio.printf_core.converter
libc.src.stdio.printf_core.core_structs
libc.src.stdio.printf_core.writer
libc.src.__support.str_to_integer
libc.src.__support.CPP.type_traits
)

add_entrypoint_object(
strtof
SRCS
Expand Down
138 changes: 138 additions & 0 deletions libc/src/stdlib/str_from_util.h
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;
Copy link
Contributor

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


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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you're just passing the format section to convert_float_hex_exp, then you'll need to set the length modifier to L for long doubles.

Copy link
Contributor Author

@vinayakdsci vinayakdsci Mar 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but wouldn't I want to do that in strfroml.cpp? I can parse the format string, get the section, and then set the length modifier to L in strfroml.cpp itself. Otherwise we would require a check for all types in the utils file.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, that would also work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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?
Thanks!

Copy link
Contributor

Choose a reason for hiding this comment

The 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 &section) {
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
42 changes: 42 additions & 0 deletions libc/src/stdlib/strfromf.cpp
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
21 changes: 21 additions & 0 deletions libc/src/stdlib/strfromf.h
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
11 changes: 10 additions & 1 deletion libc/test/src/stdlib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,16 @@ add_libc_test(
.strtol_test_support
)

add_libc_test(
strfromf_test
SUITE
libc-stdlib-tests
SRCS
strfromf_test.cpp
DEPENDS
libc.src.stdlib.strfromf
)

add_libc_test(
abs_test
SUITE
Expand Down Expand Up @@ -259,7 +269,6 @@ add_libc_test(
libc.src.stdlib.qsort
)


add_libc_test(
qsort_r_test
SUITE
Expand Down
107 changes: 107 additions & 0 deletions libc/test/src/stdlib/strfromf_test.cpp
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");
}