Skip to content

Commit 8e3b605

Browse files
[libc] Add fixed point support to printf (llvm#82707)
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.
1 parent 13fd4bf commit 8e3b605

File tree

17 files changed

+807
-13
lines changed

17 files changed

+807
-13
lines changed

libc/config/config.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
"LIBC_CONF_PRINTF_FLOAT_TO_STR_USE_MEGA_LONG_DOUBLE_TABLE": {
1616
"value": false,
1717
"doc": "Use large table for better printf long double performance."
18+
},
19+
"LIBC_CONF_PRINTF_DISABLE_FIXED_POINT": {
20+
"value": false,
21+
"doc": "Disable printing fixed point values in printf and friends."
1822
}
1923
},
2024
"string": {

libc/docs/configure.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ overrides in ``config/<platform>/config.json`` and ``config/<platform>/<arch>/co
2626
to learn about the defaults for your platform and target.
2727

2828
* **"printf" options**
29+
- ``LIBC_CONF_PRINTF_DISABLE_FIXED_POINT``: Disable printing fixed point values in printf and friends.
2930
- ``LIBC_CONF_PRINTF_DISABLE_FLOAT``: Disable printing floating point values in printf and friends.
3031
- ``LIBC_CONF_PRINTF_DISABLE_INDEX_MODE``: Disable index mode in the printf format string.
3132
- ``LIBC_CONF_PRINTF_DISABLE_WRITE_INT``: Disable handling of %n in printf format string.

libc/docs/dev/printf_behavior.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,13 @@ When set, this flag disables support for floating point numbers and all their
6262
conversions (%a, %f, %e, %g); any floating point number conversion will be
6363
treated as invalid. This reduces code size.
6464

65+
LIBC_COPT_PRINTF_DISABLE_FIXED_POINT
66+
------------------------------------
67+
When set, this flag disables support for fixed point numbers and all their
68+
conversions (%r, %k); any fixed point number conversion will be treated as
69+
invalid. This reduces code size. This has no effect if the current compiler does
70+
not support fixed point numbers.
71+
6572
LIBC_COPT_PRINTF_NO_NULLPTR_CHECKS
6673
----------------------------------
6774
When set, this flag disables the nullptr checks in %n and %s.
@@ -191,3 +198,8 @@ original conversion.
191198
The %p conversion will display a null pointer as if it was the string
192199
"(nullptr)" passed to a "%s" conversion, with all other options remaining the
193200
same as the original conversion.
201+
202+
The %r, %R, %k, and %K fixed point number format specifiers are accepted as
203+
defined in ISO/IEC TR 18037 (the fixed point number extension). These are
204+
available when the compiler is detected as having support for fixed point
205+
numbers and the LIBC_COPT_PRINTF_DISABLE_FIXED_POINT flag is not set.

libc/fuzzing/stdio/CMakeLists.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,17 @@ add_libc_fuzzer(
1515
libc.src.stdio.snprintf
1616
libc.src.__support.FPUtil.fp_bits
1717
)
18+
19+
if(LIBC_COMPILER_HAS_FIXED_POINT)
20+
add_libc_fuzzer(
21+
printf_fixed_conv_fuzz
22+
NEED_MPFR
23+
SRCS
24+
printf_fixed_conv_fuzz.cpp
25+
DEPENDS
26+
libc.src.stdio.snprintf
27+
libc.src.__support.fixed_point.fx_bits
28+
COMPILE_OPTIONS
29+
-ffixed-point # TODO: add -ffixed-point to fuzz tests automatically
30+
)
31+
endif()
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
//===-- printf_fixed_conv_fuzz.cpp ----------------------------------------===//
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+
/// Fuzzing test for llvm-libc printf %f/e/g/a implementations.
10+
///
11+
//===----------------------------------------------------------------------===//
12+
#include "src/stdio/snprintf.h"
13+
14+
#include "include/llvm-libc-macros/stdfix-macros.h"
15+
#include "src/__support/fixed_point/fx_bits.h"
16+
#include "src/__support/fixed_point/fx_rep.h"
17+
18+
#include <stddef.h>
19+
#include <stdint.h>
20+
21+
#include "utils/MPFRWrapper/mpfr_inc.h"
22+
23+
constexpr int MAX_SIZE = 10000;
24+
25+
inline bool simple_streq(char *first, char *second, int length) {
26+
for (int i = 0; i < length; ++i)
27+
if (first[i] != second[i])
28+
return false;
29+
30+
return true;
31+
}
32+
33+
inline int clamp(int num, int max) {
34+
if (num > max)
35+
return max;
36+
if (num < -max)
37+
return -max;
38+
return num;
39+
}
40+
41+
enum class TestResult {
42+
Success,
43+
BufferSizeFailed,
44+
LengthsDiffer,
45+
StringsNotEqual,
46+
};
47+
48+
template <typename F>
49+
inline TestResult test_vals(const char *fmt, uint64_t num, int prec,
50+
int width) {
51+
typename LIBC_NAMESPACE::fixed_point::FXRep<F>::StorageType raw_num = num;
52+
53+
auto raw_num_bits = LIBC_NAMESPACE::fixed_point::FXBits<F>(raw_num);
54+
55+
// This needs to be a float with enough bits of precision to hold the fixed
56+
// point number.
57+
static_assert(sizeof(long double) > sizeof(long accum));
58+
59+
// build a long double that is equivalent to the fixed point number.
60+
long double ld_num =
61+
static_cast<long double>(raw_num_bits.get_integral()) +
62+
(static_cast<long double>(raw_num_bits.get_fraction()) /
63+
static_cast<long double>(1ll << raw_num_bits.get_exponent()));
64+
65+
if (raw_num_bits.get_sign())
66+
ld_num = -ld_num;
67+
68+
// Call snprintf on a nullptr to get the buffer size.
69+
int buffer_size = LIBC_NAMESPACE::snprintf(nullptr, 0, fmt, width, prec, num);
70+
71+
if (buffer_size < 0)
72+
return TestResult::BufferSizeFailed;
73+
74+
char *test_buff = new char[buffer_size + 1];
75+
char *reference_buff = new char[buffer_size + 1];
76+
77+
int test_result = 0;
78+
int reference_result = 0;
79+
80+
test_result = LIBC_NAMESPACE::snprintf(test_buff, buffer_size + 1, fmt, width,
81+
prec, num);
82+
83+
// The fixed point format is defined to be %f equivalent.
84+
reference_result = mpfr_snprintf(reference_buff, buffer_size + 1, "%*.*Lf",
85+
width, prec, ld_num);
86+
87+
// All of these calls should return that they wrote the same amount.
88+
if (test_result != reference_result || test_result != buffer_size)
89+
return TestResult::LengthsDiffer;
90+
91+
if (!simple_streq(test_buff, reference_buff, buffer_size))
92+
return TestResult::StringsNotEqual;
93+
94+
delete[] test_buff;
95+
delete[] reference_buff;
96+
return TestResult::Success;
97+
}
98+
99+
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
100+
// const uint8_t raw_data[] = {0x8d,0x43,0x40,0x0,0x0,0x0,};
101+
// data = raw_data;
102+
// size = sizeof(raw_data);
103+
int prec = 0;
104+
int width = 0;
105+
106+
LIBC_NAMESPACE::fixed_point::FXRep<long accum>::StorageType raw_num = 0;
107+
108+
// Copy as many bytes of data as will fit into num, prec, and with. Any extras
109+
// are ignored.
110+
for (size_t cur = 0; cur < size; ++cur) {
111+
if (cur < sizeof(raw_num)) {
112+
raw_num = (raw_num << 8) + data[cur];
113+
} else if (cur < sizeof(raw_num) + sizeof(prec)) {
114+
prec = (prec << 8) + data[cur];
115+
} else if (cur < sizeof(raw_num) + sizeof(prec) + sizeof(width)) {
116+
width = (width << 8) + data[cur];
117+
}
118+
}
119+
120+
width = clamp(width, MAX_SIZE);
121+
prec = clamp(prec, MAX_SIZE);
122+
123+
TestResult result;
124+
result = test_vals<long accum>("%*.*lk", raw_num, prec, width);
125+
if (result != TestResult::Success)
126+
__builtin_trap();
127+
128+
result = test_vals<unsigned long accum>("%*.*lK", raw_num, prec, width);
129+
if (result != TestResult::Success)
130+
__builtin_trap();
131+
132+
return 0;
133+
}

libc/src/stdio/printf_core/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ endif()
1010
if(LIBC_CONF_PRINTF_FLOAT_TO_STR_USE_MEGA_LONG_DOUBLE_TABLE)
1111
list(APPEND printf_config_copts "-DLIBC_COPT_FLOAT_TO_STR_USE_MEGA_LONG_DOUBLE_TABLE")
1212
endif()
13+
if(LIBC_CONF_PRINTF_DISABLE_FIXED_POINT)
14+
list(APPEND printf_config_copts "-DLIBC_COPT_PRINTF_DISABLE_FIXED_POINT")
15+
endif()
1316
if(printf_config_copts)
1417
list(PREPEND printf_config_copts "COMPILE_OPTIONS")
1518
endif()
@@ -76,6 +79,7 @@ add_object_library(
7679
float_inf_nan_converter.h
7780
float_hex_converter.h
7881
float_dec_converter.h
82+
fixed_converter.h #TODO: Check if this should be disabled when fixed unavail
7983
DEPENDS
8084
.writer
8185
.core_structs

libc/src/stdio/printf_core/converter.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "src/stdio/printf_core/converter.h"
1010

1111
#include "src/stdio/printf_core/core_structs.h"
12+
#include "src/stdio/printf_core/printf_config.h"
1213
#include "src/stdio/printf_core/writer.h"
1314

1415
// This option allows for replacing all of the conversion functions with custom
@@ -75,6 +76,13 @@ int convert(Writer *writer, const FormatSection &to_conv) {
7576
case 'G':
7677
return convert_float_dec_auto(writer, to_conv);
7778
#endif // LIBC_COPT_PRINTF_DISABLE_FLOAT
79+
#ifdef LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
80+
case 'r':
81+
case 'R':
82+
case 'k':
83+
case 'K':
84+
return convert_fixed(writer, to_conv);
85+
#endif // LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
7886
#ifndef LIBC_COPT_PRINTF_DISABLE_WRITE_INT
7987
case 'n':
8088
return convert_write_int(writer, to_conv);

libc/src/stdio/printf_core/converter_atlas.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@
3131
#include "src/stdio/printf_core/float_hex_converter.h"
3232
#endif // LIBC_COPT_PRINTF_DISABLE_FLOAT
3333

34+
#ifdef LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
35+
// defines convert_fixed
36+
#include "src/stdio/printf_core/fixed_converter.h"
37+
#endif // LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
38+
3439
#ifndef LIBC_COPT_PRINTF_DISABLE_WRITE_INT
3540
#include "src/stdio/printf_core/write_int_converter.h"
3641
#endif // LIBC_COPT_PRINTF_DISABLE_WRITE_INT

libc/src/stdio/printf_core/converter_utils.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ LIBC_INLINE uintmax_t apply_length_modifier(uintmax_t num, LengthModifier lm) {
5151
return result; \
5252
}
5353

54+
// This is used to represent which direction the number should be rounded.
55+
enum class RoundDirection { Up, Down, Even };
56+
5457
} // namespace printf_core
5558
} // namespace LIBC_NAMESPACE
5659

libc/src/stdio/printf_core/core_structs.h

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
#define LLVM_LIBC_SRC_STDIO_PRINTF_CORE_CORE_STRUCTS_H
1111

1212
#include "src/__support/CPP/string_view.h"
13+
#include "src/__support/CPP/type_traits.h"
1314
#include "src/__support/FPUtil/FPBits.h"
15+
#include "src/stdio/printf_core/printf_config.h"
1416

1517
#include <inttypes.h>
1618
#include <stddef.h>
@@ -77,7 +79,13 @@ struct FormatSection {
7779
}
7880
};
7981

80-
enum PrimaryType : uint8_t { Unknown = 0, Float = 1, Pointer = 2, Integer = 3 };
82+
enum PrimaryType : uint8_t {
83+
Unknown = 0,
84+
Float = 1,
85+
Pointer = 2,
86+
Integer = 3,
87+
FixedPoint = 4,
88+
};
8189

8290
// TypeDesc stores the information about a type that is relevant to printf in
8391
// a relatively compact manner.
@@ -95,9 +103,16 @@ template <typename T> LIBC_INLINE constexpr TypeDesc type_desc_from_type() {
95103
} else {
96104
constexpr bool IS_POINTER = cpp::is_pointer_v<T>;
97105
constexpr bool IS_FLOAT = cpp::is_floating_point_v<T>;
98-
return TypeDesc{sizeof(T), IS_POINTER ? PrimaryType::Pointer
99-
: IS_FLOAT ? PrimaryType::Float
100-
: PrimaryType::Integer};
106+
#ifdef LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
107+
constexpr bool IS_FIXED_POINT = cpp::is_fixed_point_v<T>;
108+
#else
109+
constexpr bool IS_FIXED_POINT = false;
110+
#endif // LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
111+
112+
return TypeDesc{sizeof(T), IS_POINTER ? PrimaryType::Pointer
113+
: IS_FLOAT ? PrimaryType::Float
114+
: IS_FIXED_POINT ? PrimaryType::FixedPoint
115+
: PrimaryType::Integer};
101116
}
102117
}
103118

@@ -109,6 +124,7 @@ constexpr int FILE_WRITE_ERROR = -1;
109124
constexpr int FILE_STATUS_ERROR = -2;
110125
constexpr int NULLPTR_WRITE_ERROR = -3;
111126
constexpr int INT_CONVERSION_ERROR = -4;
127+
constexpr int FIXED_POINT_CONVERSION_ERROR = -5;
112128

113129
} // namespace printf_core
114130
} // namespace LIBC_NAMESPACE

0 commit comments

Comments
 (0)