Skip to content

Commit 6ee3cd8

Browse files
committed
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.
1 parent 8f6d7a3 commit 6ee3cd8

File tree

4 files changed

+550
-18
lines changed

4 files changed

+550
-18
lines changed

backends/verilog/verilog_backend.cc

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1163,9 +1163,9 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell)
11631163
dump_sigspec(f, cell->getPort(ID::Y));
11641164
f << stringf(" = ~((");
11651165
dump_cell_expr_port(f, cell, "A", false);
1166-
f << stringf(cell->type == ID($_AOI3_) ? " & " : " | ");
1166+
f << (cell->type == ID($_AOI3_) ? " & " : " | ");
11671167
dump_cell_expr_port(f, cell, "B", false);
1168-
f << stringf(cell->type == ID($_AOI3_) ? ") |" : ") &");
1168+
f << (cell->type == ID($_AOI3_) ? ") |" : ") &");
11691169
dump_attributes(f, "", cell->attributes, " ");
11701170
f << stringf(" ");
11711171
dump_cell_expr_port(f, cell, "C", false);
@@ -1178,13 +1178,13 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell)
11781178
dump_sigspec(f, cell->getPort(ID::Y));
11791179
f << stringf(" = ~((");
11801180
dump_cell_expr_port(f, cell, "A", false);
1181-
f << stringf(cell->type == ID($_AOI4_) ? " & " : " | ");
1181+
f << (cell->type == ID($_AOI4_) ? " & " : " | ");
11821182
dump_cell_expr_port(f, cell, "B", false);
1183-
f << stringf(cell->type == ID($_AOI4_) ? ") |" : ") &");
1183+
f << (cell->type == ID($_AOI4_) ? ") |" : ") &");
11841184
dump_attributes(f, "", cell->attributes, " ");
11851185
f << stringf(" (");
11861186
dump_cell_expr_port(f, cell, "C", false);
1187-
f << stringf(cell->type == ID($_AOI4_) ? " & " : " | ");
1187+
f << (cell->type == ID($_AOI4_) ? " & " : " | ");
11881188
dump_cell_expr_port(f, cell, "D", false);
11891189
f << stringf("));\n");
11901190
return true;
@@ -1395,10 +1395,10 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell)
13951395
int s_width = cell->getPort(ID::S).size();
13961396
std::string func_name = cellname(cell);
13971397

1398-
f << stringf("%s" "function [%d:0] %s;\n", indent.c_str(), width-1, func_name.c_str());
1399-
f << stringf("%s" " input [%d:0] a;\n", indent.c_str(), width-1);
1400-
f << stringf("%s" " input [%d:0] b;\n", indent.c_str(), s_width*width-1);
1401-
f << stringf("%s" " input [%d:0] s;\n", indent.c_str(), s_width-1);
1398+
f << stringf("%s" "function [%d:0] %s;\n", indent, width-1, func_name);
1399+
f << stringf("%s" " input [%d:0] a;\n", indent, width-1);
1400+
f << stringf("%s" " input [%d:0] b;\n", indent, s_width*width-1);
1401+
f << stringf("%s" " input [%d:0] s;\n", indent, s_width-1);
14021402

14031403
dump_attributes(f, indent + " ", cell->attributes);
14041404
if (noparallelcase)
@@ -1407,7 +1407,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell)
14071407
if (!noattr)
14081408
f << stringf("%s" " (* parallel_case *)\n", indent.c_str());
14091409
f << stringf("%s" " casez (s)", indent.c_str());
1410-
f << stringf(noattr ? " // synopsys parallel_case\n" : "\n");
1410+
f << (noattr ? " // synopsys parallel_case\n" : "\n");
14111411
}
14121412

14131413
for (int i = 0; i < s_width; i++)

kernel/io.cc

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,4 +384,153 @@ std::string escape_filename_spaces(const std::string& filename)
384384
return out;
385385
}
386386

387+
void format_emit_unescaped(std::string &result, std::string_view fmt)
388+
{
389+
result.reserve(result.size() + fmt.size());
390+
for (size_t i = 0; i < fmt.size(); ++i) {
391+
char ch = fmt[i];
392+
result.push_back(ch);
393+
if (ch == '%' && i + 1 < fmt.size() && fmt[i + 1] == '%') {
394+
++i;
395+
}
396+
}
397+
}
398+
399+
std::string unescape_format_string(std::string_view fmt)
400+
{
401+
std::string result;
402+
format_emit_unescaped(result, fmt);
403+
return result;
404+
}
405+
406+
static std::string string_view_stringf(std::string_view spec, ...)
407+
{
408+
std::string fmt(spec);
409+
char format_specifier = fmt[fmt.size() - 1];
410+
switch (format_specifier) {
411+
case 'd':
412+
case 'i':
413+
case 'o':
414+
case 'u':
415+
case 'x':
416+
case 'X': {
417+
// Strip any length modifier off `fmt`
418+
std::string long_fmt;
419+
for (size_t i = 0; i + 1 < fmt.size(); ++i) {
420+
char ch = fmt[i];
421+
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
422+
break;
423+
}
424+
long_fmt.push_back(ch);
425+
}
426+
// Add `lld` or whatever
427+
long_fmt += "ll";
428+
long_fmt.push_back(format_specifier);
429+
fmt = long_fmt;
430+
break;
431+
}
432+
default:
433+
break;
434+
}
435+
436+
va_list ap;
437+
va_start(ap, spec);
438+
std::string result = vstringf(fmt.c_str(), ap);
439+
va_end(ap);
440+
return result;
441+
}
442+
443+
template <typename Arg>
444+
static void format_emit_stringf(std::string &result, std::string_view spec, int *dynamic_ints,
445+
DynamicIntCount num_dynamic_ints, Arg arg)
446+
{
447+
// Delegate nontrivial formats to the C library.
448+
switch (num_dynamic_ints) {
449+
case DynamicIntCount::NONE:
450+
result += string_view_stringf(spec, arg);
451+
return;
452+
case DynamicIntCount::ONE:
453+
result += string_view_stringf(spec, dynamic_ints[0], arg);
454+
return;
455+
case DynamicIntCount::TWO:
456+
result += string_view_stringf(spec, dynamic_ints[0], dynamic_ints[1], arg);
457+
return;
458+
}
459+
YOSYS_ABORT("Internal error");
460+
}
461+
462+
void format_emit_long_long(std::string &result, std::string_view spec, int *dynamic_ints,
463+
DynamicIntCount num_dynamic_ints, long long arg)
464+
{
465+
if (spec == "%d") {
466+
// Format checking will have guaranteed num_dynamic_ints == 0.
467+
result += std::to_string(arg);
468+
return;
469+
}
470+
format_emit_stringf(result, spec, dynamic_ints, num_dynamic_ints, arg);
471+
}
472+
473+
void format_emit_unsigned_long_long(std::string &result, std::string_view spec, int *dynamic_ints,
474+
DynamicIntCount num_dynamic_ints, unsigned long long arg)
475+
{
476+
if (spec == "%u") {
477+
// Format checking will have guaranteed num_dynamic_ints == 0.
478+
result += std::to_string(arg);
479+
return;
480+
}
481+
if (spec == "%c") {
482+
result += static_cast<char>(arg);
483+
return;
484+
}
485+
format_emit_stringf(result, spec, dynamic_ints, num_dynamic_ints, arg);
486+
}
487+
488+
void format_emit_double(std::string &result, std::string_view spec, int *dynamic_ints,
489+
DynamicIntCount num_dynamic_ints, double arg)
490+
{
491+
format_emit_stringf(result, spec, dynamic_ints, num_dynamic_ints, arg);
492+
}
493+
494+
void format_emit_char_ptr(std::string &result, std::string_view spec, int *dynamic_ints,
495+
DynamicIntCount num_dynamic_ints, const char *arg)
496+
{
497+
if (spec == "%s") {
498+
// Format checking will have guaranteed num_dynamic_ints == 0.
499+
result += arg;
500+
return;
501+
}
502+
format_emit_stringf(result, spec, dynamic_ints, num_dynamic_ints, arg);
503+
}
504+
505+
void format_emit_string(std::string &result, std::string_view spec, int *dynamic_ints,
506+
DynamicIntCount num_dynamic_ints, const std::string &arg)
507+
{
508+
if (spec == "%s") {
509+
// Format checking will have guaranteed num_dynamic_ints == 0.
510+
result += arg;
511+
return;
512+
}
513+
format_emit_stringf(result, spec, dynamic_ints, num_dynamic_ints, arg.c_str());
514+
}
515+
516+
void format_emit_string_view(std::string &result, std::string_view spec, int *dynamic_ints,
517+
DynamicIntCount num_dynamic_ints, std::string_view arg)
518+
{
519+
if (spec == "%s") {
520+
// Format checking will have guaranteed num_dynamic_ints == 0.
521+
// We can output the string without creating a temporary copy.
522+
result += arg;
523+
return;
524+
}
525+
// Delegate nontrivial formats to the C library. We need to construct
526+
// a temporary string to ensure null termination.
527+
format_emit_stringf(result, spec, dynamic_ints, num_dynamic_ints, std::string(arg).c_str());
528+
}
529+
530+
void format_emit_void_ptr(std::string &result, std::string_view spec, int *dynamic_ints,
531+
DynamicIntCount num_dynamic_ints, const void *arg)
532+
{
533+
format_emit_stringf(result, spec, dynamic_ints, num_dynamic_ints, arg);
534+
}
535+
387536
YOSYS_NAMESPACE_END

0 commit comments

Comments
 (0)