Skip to content

Commit

Permalink
[libc] Add fixed point support to printf (#82707)
Browse files Browse the repository at this point in the history
This patch adds the r, R, k, and K conversion specifiers to printf, with
accompanying tests. They are guarded behind the
LIBC_COPT_PRINTF_DISABLE_FIXED_POINT flag as well as automatic fixed
point support detection.
  • Loading branch information
michaelrj-google committed Feb 27, 2024
1 parent 13fd4bf commit 8e3b605
Show file tree
Hide file tree
Showing 17 changed files with 807 additions and 13 deletions.
4 changes: 4 additions & 0 deletions libc/config/config.json
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
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
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
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
@@ -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;
}

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
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
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
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
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
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

0 comments on commit 8e3b605

Please sign in to comment.