Skip to content

[libc] Add fixed point support to printf #82707

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 6 commits into from
Feb 27, 2024
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
4 changes: 4 additions & 0 deletions libc/config/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
"LIBC_CONF_PRINTF_FLOAT_TO_STR_USE_MEGA_LONG_DOUBLE_TABLE": {
"value": false,
"doc": "Use large table for better printf long double performance."
},
"LIBC_CONF_PRINTF_DISABLE_FIXED_POINT": {
"value": false,
"doc": "Disable printing fixed point values in printf and friends."
}
},
"string": {
Expand Down
1 change: 1 addition & 0 deletions libc/docs/configure.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ overrides in ``config/<platform>/config.json`` and ``config/<platform>/<arch>/co
to learn about the defaults for your platform and target.

* **"printf" options**
- ``LIBC_CONF_PRINTF_DISABLE_FIXED_POINT``: Disable printing fixed point values in printf and friends.
- ``LIBC_CONF_PRINTF_DISABLE_FLOAT``: Disable printing floating point values in printf and friends.
- ``LIBC_CONF_PRINTF_DISABLE_INDEX_MODE``: Disable index mode in the printf format string.
- ``LIBC_CONF_PRINTF_DISABLE_WRITE_INT``: Disable handling of %n in printf format string.
Expand Down
12 changes: 12 additions & 0 deletions libc/docs/dev/printf_behavior.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ When set, this flag disables support for floating point numbers and all their
conversions (%a, %f, %e, %g); any floating point number conversion will be
treated as invalid. This reduces code size.

LIBC_COPT_PRINTF_DISABLE_FIXED_POINT
------------------------------------
When set, this flag disables support for fixed point numbers and all their
conversions (%r, %k); any fixed point number conversion will be treated as
invalid. This reduces code size. This has no effect if the current compiler does
not support fixed point numbers.

LIBC_COPT_PRINTF_NO_NULLPTR_CHECKS
----------------------------------
When set, this flag disables the nullptr checks in %n and %s.
Expand Down Expand Up @@ -191,3 +198,8 @@ original conversion.
The %p conversion will display a null pointer as if it was the string
"(nullptr)" passed to a "%s" conversion, with all other options remaining the
same as the original conversion.

The %r, %R, %k, and %K fixed point number format specifiers are accepted as
defined in ISO/IEC TR 18037 (the fixed point number extension). These are
available when the compiler is detected as having support for fixed point
numbers and the LIBC_COPT_PRINTF_DISABLE_FIXED_POINT flag is not set.
14 changes: 14 additions & 0 deletions libc/fuzzing/stdio/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,17 @@ add_libc_fuzzer(
libc.src.stdio.snprintf
libc.src.__support.FPUtil.fp_bits
)

if(LIBC_COMPILER_HAS_FIXED_POINT)
add_libc_fuzzer(
printf_fixed_conv_fuzz
NEED_MPFR
SRCS
printf_fixed_conv_fuzz.cpp
DEPENDS
libc.src.stdio.snprintf
libc.src.__support.fixed_point.fx_bits
COMPILE_OPTIONS
-ffixed-point # TODO: add -ffixed-point to fuzz tests automatically
)
endif()
133 changes: 133 additions & 0 deletions libc/fuzzing/stdio/printf_fixed_conv_fuzz.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
//===-- printf_fixed_conv_fuzz.cpp ----------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
///
/// Fuzzing test for llvm-libc printf %f/e/g/a implementations.
///
//===----------------------------------------------------------------------===//
#include "src/stdio/snprintf.h"

#include "include/llvm-libc-macros/stdfix-macros.h"
#include "src/__support/fixed_point/fx_bits.h"
#include "src/__support/fixed_point/fx_rep.h"

#include <stddef.h>
#include <stdint.h>

#include "utils/MPFRWrapper/mpfr_inc.h"

constexpr int MAX_SIZE = 10000;

inline bool simple_streq(char *first, char *second, int length) {
for (int i = 0; i < length; ++i)
if (first[i] != second[i])
return false;

return true;
}

inline int clamp(int num, int max) {
if (num > max)
return max;
if (num < -max)
return -max;
return num;
}
Comment on lines +33 to +39
Copy link
Member

Choose a reason for hiding this comment

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

At some point, it might be worthwhile to add something akin to std::clamp to libc/src/__support/CPP/algorithm.h.


enum class TestResult {
Success,
BufferSizeFailed,
LengthsDiffer,
StringsNotEqual,
};

template <typename F>
inline TestResult test_vals(const char *fmt, uint64_t num, int prec,
int width) {
typename LIBC_NAMESPACE::fixed_point::FXRep<F>::StorageType raw_num = num;

auto raw_num_bits = LIBC_NAMESPACE::fixed_point::FXBits<F>(raw_num);

// This needs to be a float with enough bits of precision to hold the fixed
// point number.
static_assert(sizeof(long double) > sizeof(long accum));

// build a long double that is equivalent to the fixed point number.
long double ld_num =
static_cast<long double>(raw_num_bits.get_integral()) +
(static_cast<long double>(raw_num_bits.get_fraction()) /
static_cast<long double>(1ll << raw_num_bits.get_exponent()));

if (raw_num_bits.get_sign())
ld_num = -ld_num;

// Call snprintf on a nullptr to get the buffer size.
int buffer_size = LIBC_NAMESPACE::snprintf(nullptr, 0, fmt, width, prec, num);

if (buffer_size < 0)
return TestResult::BufferSizeFailed;

char *test_buff = new char[buffer_size + 1];
char *reference_buff = new char[buffer_size + 1];

int test_result = 0;
int reference_result = 0;

test_result = LIBC_NAMESPACE::snprintf(test_buff, buffer_size + 1, fmt, width,
prec, num);

// The fixed point format is defined to be %f equivalent.
reference_result = mpfr_snprintf(reference_buff, buffer_size + 1, "%*.*Lf",
width, prec, ld_num);

// All of these calls should return that they wrote the same amount.
if (test_result != reference_result || test_result != buffer_size)
return TestResult::LengthsDiffer;

if (!simple_streq(test_buff, reference_buff, buffer_size))
return TestResult::StringsNotEqual;

delete[] test_buff;
delete[] reference_buff;
return TestResult::Success;
}

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// const uint8_t raw_data[] = {0x8d,0x43,0x40,0x0,0x0,0x0,};
// data = raw_data;
// size = sizeof(raw_data);
int prec = 0;
int width = 0;

LIBC_NAMESPACE::fixed_point::FXRep<long accum>::StorageType raw_num = 0;

// Copy as many bytes of data as will fit into num, prec, and with. Any extras
// are ignored.
for (size_t cur = 0; cur < size; ++cur) {
if (cur < sizeof(raw_num)) {
raw_num = (raw_num << 8) + data[cur];
} else if (cur < sizeof(raw_num) + sizeof(prec)) {
prec = (prec << 8) + data[cur];
} else if (cur < sizeof(raw_num) + sizeof(prec) + sizeof(width)) {
width = (width << 8) + data[cur];
}
}

width = clamp(width, MAX_SIZE);
prec = clamp(prec, MAX_SIZE);

TestResult result;
result = test_vals<long accum>("%*.*lk", raw_num, prec, width);
if (result != TestResult::Success)
__builtin_trap();

result = test_vals<unsigned long accum>("%*.*lK", raw_num, prec, width);
if (result != TestResult::Success)
__builtin_trap();

return 0;
}
4 changes: 4 additions & 0 deletions libc/src/stdio/printf_core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ endif()
if(LIBC_CONF_PRINTF_FLOAT_TO_STR_USE_MEGA_LONG_DOUBLE_TABLE)
list(APPEND printf_config_copts "-DLIBC_COPT_FLOAT_TO_STR_USE_MEGA_LONG_DOUBLE_TABLE")
endif()
if(LIBC_CONF_PRINTF_DISABLE_FIXED_POINT)
list(APPEND printf_config_copts "-DLIBC_COPT_PRINTF_DISABLE_FIXED_POINT")
endif()
if(printf_config_copts)
list(PREPEND printf_config_copts "COMPILE_OPTIONS")
endif()
Expand Down Expand Up @@ -76,6 +79,7 @@ add_object_library(
float_inf_nan_converter.h
float_hex_converter.h
float_dec_converter.h
fixed_converter.h #TODO: Check if this should be disabled when fixed unavail
DEPENDS
.writer
.core_structs
Expand Down
8 changes: 8 additions & 0 deletions libc/src/stdio/printf_core/converter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "src/stdio/printf_core/converter.h"

#include "src/stdio/printf_core/core_structs.h"
#include "src/stdio/printf_core/printf_config.h"
#include "src/stdio/printf_core/writer.h"

// This option allows for replacing all of the conversion functions with custom
Expand Down Expand Up @@ -75,6 +76,13 @@ int convert(Writer *writer, const FormatSection &to_conv) {
case 'G':
return convert_float_dec_auto(writer, to_conv);
#endif // LIBC_COPT_PRINTF_DISABLE_FLOAT
#ifdef LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
case 'r':
case 'R':
case 'k':
case 'K':
return convert_fixed(writer, to_conv);
#endif // LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
#ifndef LIBC_COPT_PRINTF_DISABLE_WRITE_INT
case 'n':
return convert_write_int(writer, to_conv);
Expand Down
5 changes: 5 additions & 0 deletions libc/src/stdio/printf_core/converter_atlas.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@
#include "src/stdio/printf_core/float_hex_converter.h"
#endif // LIBC_COPT_PRINTF_DISABLE_FLOAT

#ifdef LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
// defines convert_fixed
#include "src/stdio/printf_core/fixed_converter.h"
#endif // LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT

#ifndef LIBC_COPT_PRINTF_DISABLE_WRITE_INT
#include "src/stdio/printf_core/write_int_converter.h"
#endif // LIBC_COPT_PRINTF_DISABLE_WRITE_INT
Expand Down
3 changes: 3 additions & 0 deletions libc/src/stdio/printf_core/converter_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ LIBC_INLINE uintmax_t apply_length_modifier(uintmax_t num, LengthModifier lm) {
return result; \
}

// This is used to represent which direction the number should be rounded.
enum class RoundDirection { Up, Down, Even };

} // namespace printf_core
} // namespace LIBC_NAMESPACE

Expand Down
24 changes: 20 additions & 4 deletions libc/src/stdio/printf_core/core_structs.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
#define LLVM_LIBC_SRC_STDIO_PRINTF_CORE_CORE_STRUCTS_H

#include "src/__support/CPP/string_view.h"
#include "src/__support/CPP/type_traits.h"
#include "src/__support/FPUtil/FPBits.h"
#include "src/stdio/printf_core/printf_config.h"

#include <inttypes.h>
#include <stddef.h>
Expand Down Expand Up @@ -77,7 +79,13 @@ struct FormatSection {
}
};

enum PrimaryType : uint8_t { Unknown = 0, Float = 1, Pointer = 2, Integer = 3 };
enum PrimaryType : uint8_t {
Unknown = 0,
Float = 1,
Pointer = 2,
Integer = 3,
FixedPoint = 4,
};

// TypeDesc stores the information about a type that is relevant to printf in
// a relatively compact manner.
Expand All @@ -95,9 +103,16 @@ template <typename T> LIBC_INLINE constexpr TypeDesc type_desc_from_type() {
} else {
constexpr bool IS_POINTER = cpp::is_pointer_v<T>;
constexpr bool IS_FLOAT = cpp::is_floating_point_v<T>;
return TypeDesc{sizeof(T), IS_POINTER ? PrimaryType::Pointer
: IS_FLOAT ? PrimaryType::Float
: PrimaryType::Integer};
#ifdef LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
constexpr bool IS_FIXED_POINT = cpp::is_fixed_point_v<T>;
#else
constexpr bool IS_FIXED_POINT = false;
#endif // LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT

return TypeDesc{sizeof(T), IS_POINTER ? PrimaryType::Pointer
: IS_FLOAT ? PrimaryType::Float
: IS_FIXED_POINT ? PrimaryType::FixedPoint
: PrimaryType::Integer};
}
}

Expand All @@ -109,6 +124,7 @@ constexpr int FILE_WRITE_ERROR = -1;
constexpr int FILE_STATUS_ERROR = -2;
constexpr int NULLPTR_WRITE_ERROR = -3;
constexpr int INT_CONVERSION_ERROR = -4;
constexpr int FIXED_POINT_CONVERSION_ERROR = -5;

} // namespace printf_core
} // namespace LIBC_NAMESPACE
Expand Down
Loading