mirror of
https://github.com/YosysHQ/yosys
synced 2025-08-04 02:10:24 +00:00
Replace stringf()
with a templated function which does compile-time format string checking.
Checking only happens at compile time if -std=c++20 (or greater) is enabled. Otherwise the checking happens at run time. This requires the format string to be a compile-time constant (when compiling with C++20), so fix a few places where that isn't true. The format string behavior is a bit more lenient than C printf. For %d/%u you can pass any integer type and it will be converted and output without truncating bits, i.e. any length specifier is ignored and the conversion is always treated as 'll'. Any truncation needs to be done by casting the argument itself. For %f/%g you can pass anything that converts to double, including integers. Performance results with clang 19 -O3 on Linux: ``` hyperfine './yosys -dp "read_rtlil /usr/local/google/home/rocallahan/Downloads/jpeg.synth.il; dump"' ``` C++17 before: Time (mean ± σ): 101.3 ms ± 0.8 ms [User: 85.6 ms, System: 15.6 ms] C++17 after: Time (mean ± σ): 98.4 ms ± 1.2 ms [User: 82.1 ms, System: 16.1 ms] C++20 before: Time (mean ± σ): 100.9 ms ± 1.1 ms [User: 87.0 ms, System: 13.8 ms] C++20 after: Time (mean ± σ): 97.8 ms ± 1.4 ms [User: 83.1 ms, System: 14.7 ms] The generated code is reasonably efficient. E.g. with clang 19, `stringf()` with a format with no %% escapes and no other parameters (a weirdly common case) often compiles to a fully inlined `std::string` construction. In general the format string parsing is often (not always) compiled away.
This commit is contained in:
parent
8f6d7a3043
commit
6ee3cd8ffd
4 changed files with 551 additions and 19 deletions
149
kernel/io.cc
149
kernel/io.cc
|
@ -384,4 +384,153 @@ std::string escape_filename_spaces(const std::string& filename)
|
|||
return out;
|
||||
}
|
||||
|
||||
void format_emit_unescaped(std::string &result, std::string_view fmt)
|
||||
{
|
||||
result.reserve(result.size() + fmt.size());
|
||||
for (size_t i = 0; i < fmt.size(); ++i) {
|
||||
char ch = fmt[i];
|
||||
result.push_back(ch);
|
||||
if (ch == '%' && i + 1 < fmt.size() && fmt[i + 1] == '%') {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string unescape_format_string(std::string_view fmt)
|
||||
{
|
||||
std::string result;
|
||||
format_emit_unescaped(result, fmt);
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::string string_view_stringf(std::string_view spec, ...)
|
||||
{
|
||||
std::string fmt(spec);
|
||||
char format_specifier = fmt[fmt.size() - 1];
|
||||
switch (format_specifier) {
|
||||
case 'd':
|
||||
case 'i':
|
||||
case 'o':
|
||||
case 'u':
|
||||
case 'x':
|
||||
case 'X': {
|
||||
// Strip any length modifier off `fmt`
|
||||
std::string long_fmt;
|
||||
for (size_t i = 0; i + 1 < fmt.size(); ++i) {
|
||||
char ch = fmt[i];
|
||||
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
|
||||
break;
|
||||
}
|
||||
long_fmt.push_back(ch);
|
||||
}
|
||||
// Add `lld` or whatever
|
||||
long_fmt += "ll";
|
||||
long_fmt.push_back(format_specifier);
|
||||
fmt = long_fmt;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, spec);
|
||||
std::string result = vstringf(fmt.c_str(), ap);
|
||||
va_end(ap);
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename Arg>
|
||||
static void format_emit_stringf(std::string &result, std::string_view spec, int *dynamic_ints,
|
||||
DynamicIntCount num_dynamic_ints, Arg arg)
|
||||
{
|
||||
// Delegate nontrivial formats to the C library.
|
||||
switch (num_dynamic_ints) {
|
||||
case DynamicIntCount::NONE:
|
||||
result += string_view_stringf(spec, arg);
|
||||
return;
|
||||
case DynamicIntCount::ONE:
|
||||
result += string_view_stringf(spec, dynamic_ints[0], arg);
|
||||
return;
|
||||
case DynamicIntCount::TWO:
|
||||
result += string_view_stringf(spec, dynamic_ints[0], dynamic_ints[1], arg);
|
||||
return;
|
||||
}
|
||||
YOSYS_ABORT("Internal error");
|
||||
}
|
||||
|
||||
void format_emit_long_long(std::string &result, std::string_view spec, int *dynamic_ints,
|
||||
DynamicIntCount num_dynamic_ints, long long arg)
|
||||
{
|
||||
if (spec == "%d") {
|
||||
// Format checking will have guaranteed num_dynamic_ints == 0.
|
||||
result += std::to_string(arg);
|
||||
return;
|
||||
}
|
||||
format_emit_stringf(result, spec, dynamic_ints, num_dynamic_ints, arg);
|
||||
}
|
||||
|
||||
void format_emit_unsigned_long_long(std::string &result, std::string_view spec, int *dynamic_ints,
|
||||
DynamicIntCount num_dynamic_ints, unsigned long long arg)
|
||||
{
|
||||
if (spec == "%u") {
|
||||
// Format checking will have guaranteed num_dynamic_ints == 0.
|
||||
result += std::to_string(arg);
|
||||
return;
|
||||
}
|
||||
if (spec == "%c") {
|
||||
result += static_cast<char>(arg);
|
||||
return;
|
||||
}
|
||||
format_emit_stringf(result, spec, dynamic_ints, num_dynamic_ints, arg);
|
||||
}
|
||||
|
||||
void format_emit_double(std::string &result, std::string_view spec, int *dynamic_ints,
|
||||
DynamicIntCount num_dynamic_ints, double arg)
|
||||
{
|
||||
format_emit_stringf(result, spec, dynamic_ints, num_dynamic_ints, arg);
|
||||
}
|
||||
|
||||
void format_emit_char_ptr(std::string &result, std::string_view spec, int *dynamic_ints,
|
||||
DynamicIntCount num_dynamic_ints, const char *arg)
|
||||
{
|
||||
if (spec == "%s") {
|
||||
// Format checking will have guaranteed num_dynamic_ints == 0.
|
||||
result += arg;
|
||||
return;
|
||||
}
|
||||
format_emit_stringf(result, spec, dynamic_ints, num_dynamic_ints, arg);
|
||||
}
|
||||
|
||||
void format_emit_string(std::string &result, std::string_view spec, int *dynamic_ints,
|
||||
DynamicIntCount num_dynamic_ints, const std::string &arg)
|
||||
{
|
||||
if (spec == "%s") {
|
||||
// Format checking will have guaranteed num_dynamic_ints == 0.
|
||||
result += arg;
|
||||
return;
|
||||
}
|
||||
format_emit_stringf(result, spec, dynamic_ints, num_dynamic_ints, arg.c_str());
|
||||
}
|
||||
|
||||
void format_emit_string_view(std::string &result, std::string_view spec, int *dynamic_ints,
|
||||
DynamicIntCount num_dynamic_ints, std::string_view arg)
|
||||
{
|
||||
if (spec == "%s") {
|
||||
// Format checking will have guaranteed num_dynamic_ints == 0.
|
||||
// We can output the string without creating a temporary copy.
|
||||
result += arg;
|
||||
return;
|
||||
}
|
||||
// Delegate nontrivial formats to the C library. We need to construct
|
||||
// a temporary string to ensure null termination.
|
||||
format_emit_stringf(result, spec, dynamic_ints, num_dynamic_ints, std::string(arg).c_str());
|
||||
}
|
||||
|
||||
void format_emit_void_ptr(std::string &result, std::string_view spec, int *dynamic_ints,
|
||||
DynamicIntCount num_dynamic_ints, const void *arg)
|
||||
{
|
||||
format_emit_stringf(result, spec, dynamic_ints, num_dynamic_ints, arg);
|
||||
}
|
||||
|
||||
YOSYS_NAMESPACE_END
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue