diff --git a/backends/verilog/verilog_backend.cc b/backends/verilog/verilog_backend.cc index 144dad90c..070df1543 100644 --- a/backends/verilog/verilog_backend.cc +++ b/backends/verilog/verilog_backend.cc @@ -1163,9 +1163,9 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) dump_sigspec(f, cell->getPort(ID::Y)); f << stringf(" = ~(("); dump_cell_expr_port(f, cell, "A", false); - f << stringf(cell->type == ID($_AOI3_) ? " & " : " | "); + f << (cell->type == ID($_AOI3_) ? " & " : " | "); dump_cell_expr_port(f, cell, "B", false); - f << stringf(cell->type == ID($_AOI3_) ? ") |" : ") &"); + f << (cell->type == ID($_AOI3_) ? ") |" : ") &"); dump_attributes(f, "", cell->attributes, " "); f << stringf(" "); 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) dump_sigspec(f, cell->getPort(ID::Y)); f << stringf(" = ~(("); dump_cell_expr_port(f, cell, "A", false); - f << stringf(cell->type == ID($_AOI4_) ? " & " : " | "); + f << (cell->type == ID($_AOI4_) ? " & " : " | "); dump_cell_expr_port(f, cell, "B", false); - f << stringf(cell->type == ID($_AOI4_) ? ") |" : ") &"); + f << (cell->type == ID($_AOI4_) ? ") |" : ") &"); dump_attributes(f, "", cell->attributes, " "); f << stringf(" ("); dump_cell_expr_port(f, cell, "C", false); - f << stringf(cell->type == ID($_AOI4_) ? " & " : " | "); + f << (cell->type == ID($_AOI4_) ? " & " : " | "); dump_cell_expr_port(f, cell, "D", false); f << stringf("));\n"); return true; @@ -1395,10 +1395,10 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) int s_width = cell->getPort(ID::S).size(); std::string func_name = cellname(cell); - f << stringf("%s" "function [%d:0] %s;\n", indent.c_str(), width-1, func_name.c_str()); - f << stringf("%s" " input [%d:0] a;\n", indent.c_str(), width-1); - f << stringf("%s" " input [%d:0] b;\n", indent.c_str(), s_width*width-1); - f << stringf("%s" " input [%d:0] s;\n", indent.c_str(), s_width-1); + f << stringf("%s" "function [%d:0] %s;\n", indent, width-1, func_name); + f << stringf("%s" " input [%d:0] a;\n", indent, width-1); + f << stringf("%s" " input [%d:0] b;\n", indent, s_width*width-1); + f << stringf("%s" " input [%d:0] s;\n", indent, s_width-1); dump_attributes(f, indent + " ", cell->attributes); if (noparallelcase) @@ -1407,7 +1407,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) if (!noattr) f << stringf("%s" " (* parallel_case *)\n", indent.c_str()); f << stringf("%s" " casez (s)", indent.c_str()); - f << stringf(noattr ? " // synopsys parallel_case\n" : "\n"); + f << (noattr ? " // synopsys parallel_case\n" : "\n"); } for (int i = 0; i < s_width; i++) diff --git a/kernel/io.cc b/kernel/io.cc index d7d126c4c..3deb54d86 100644 --- a/kernel/io.cc +++ b/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 +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(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 diff --git a/kernel/io.h b/kernel/io.h index 91699d775..ea2499e43 100644 --- a/kernel/io.h +++ b/kernel/io.h @@ -1,5 +1,6 @@ #include #include +#include #include "kernel/yosys_common.h" #ifndef YOSYS_IO_H @@ -50,18 +51,391 @@ inline std::string vstringf(const char *fmt, va_list ap) #endif } -std::string stringf(const char *fmt, ...) YS_ATTRIBUTE(format(printf, 1, 2)); - -inline std::string stringf(const char *fmt, ...) +enum ConversionSpecifier : uint8_t { - std::string string; - va_list ap; + CONVSPEC_NONE, + // Specifier not understood/supported + CONVSPEC_ERROR, + // Consumes a "long long" + CONVSPEC_SIGNED_INT, + // Consumes a "unsigned long long" + CONVSPEC_UNSIGNED_INT, + // Consumes a "double" + CONVSPEC_DOUBLE, + // Consumes a "const char*" + CONVSPEC_CHAR_PTR, + // Consumes a "void*" + CONVSPEC_VOID_PTR, +}; - va_start(ap, fmt); - string = vstringf(fmt, ap); - va_end(ap); +constexpr ConversionSpecifier parse_conversion_specifier(char ch, char prev_ch) +{ + switch (ch) { + case 'd': + case 'i': + return CONVSPEC_SIGNED_INT; + case 'o': + case 'u': + case 'x': + case 'X': + case 'm': + return CONVSPEC_UNSIGNED_INT; + case 'c': + if (prev_ch == 'l' || prev_ch == 'q' || prev_ch == 'L') { + // wchar not supported + return CONVSPEC_ERROR; + } + return CONVSPEC_UNSIGNED_INT; + case 'e': + case 'E': + case 'f': + case 'F': + case 'g': + case 'G': + case 'a': + case 'A': + return CONVSPEC_DOUBLE; + case 's': + if (prev_ch == 'l' || prev_ch == 'q' || prev_ch == 'L') { + // wchar not supported + return CONVSPEC_ERROR; + } + return CONVSPEC_CHAR_PTR; + case 'p': + return CONVSPEC_VOID_PTR; + case '$': // positional parameters + case 'n': + case 'S': + return CONVSPEC_ERROR; + default: + return CONVSPEC_NONE; + } +} - return string; +enum class DynamicIntCount : uint8_t { + NONE = 0, + ONE = 1, + TWO = 2, +}; + +// Describes a printf-style format conversion specifier found in a format string. +struct FoundFormatSpec +{ + // The start offset of the conversion spec in the format string. + int start; + // The end offset of the conversion spec in the format string. + int end; + ConversionSpecifier spec; + // Number of int args consumed by '*' dynamic width/precision args. + DynamicIntCount num_dynamic_ints; +}; + +// Ensure there is no format spec. +constexpr void ensure_no_format_spec(std::string_view fmt, int index, bool *has_escapes) +{ + int fmt_size = static_cast(fmt.size()); + // A trailing '%' is not a format spec. + while (index + 1 < fmt_size) { + if (fmt[index] != '%') { + ++index; + continue; + } + if (fmt[index + 1] != '%') { + YOSYS_ABORT("More format conversion specifiers than arguments"); + } + *has_escapes = true; + index += 2; + } +} + +// Returns the next format conversion specifier (starting with '%'). +// Returns CONVSPEC_NONE if there isn't a format conversion specifier. +constexpr FoundFormatSpec find_next_format_spec(std::string_view fmt, int fmt_start, bool *has_escapes) +{ + int index = fmt_start; + int fmt_size = static_cast(fmt.size()); + while (index < fmt_size) { + if (fmt[index] != '%') { + ++index; + continue; + } + int p = index + 1; + uint8_t num_dynamic_ints = 0; + while (true) { + if (p == fmt_size) { + return {0, 0, CONVSPEC_NONE, DynamicIntCount::NONE}; + } + if (fmt[p] == '%') { + *has_escapes = true; + index = p + 1; + break; + } + if (fmt[p] == '*') { + if (num_dynamic_ints >= 2) { + return {0, 0, CONVSPEC_ERROR, DynamicIntCount::NONE}; + } + ++num_dynamic_ints; + } + ConversionSpecifier spec = parse_conversion_specifier(fmt[p], fmt[p - 1]); + if (spec != CONVSPEC_NONE) { + return {index, p + 1, spec, static_cast(num_dynamic_ints)}; + } + ++p; + } + } + return {0, 0, CONVSPEC_NONE, DynamicIntCount::NONE}; +} + +template +constexpr typename std::enable_if::type +check_format(std::string_view fmt, int fmt_start, bool *has_escapes, FoundFormatSpec*, DynamicIntCount) +{ + ensure_no_format_spec(fmt, fmt_start, has_escapes); +} + +// Check that the format string `fmt.substr(fmt_start)` is valid for the given type arguments. +// Fills `specs` with the FoundFormatSpecs found in the format string. +// `int_args_consumed` is the number of int arguments already consumed to satisfy the +// dynamic width/precision args for the next format conversion specifier. +template +constexpr void check_format(std::string_view fmt, int fmt_start, bool *has_escapes, FoundFormatSpec* specs, + DynamicIntCount int_args_consumed) +{ + FoundFormatSpec found = find_next_format_spec(fmt, fmt_start, has_escapes); + if (found.num_dynamic_ints > int_args_consumed) { + // We need to consume at least one more int for the dynamic + // width/precision of this format conversion specifier. + if constexpr (!std::is_convertible_v) { + YOSYS_ABORT("Expected dynamic int argument"); + } + check_format(fmt, fmt_start, has_escapes, specs, + static_cast(static_cast(int_args_consumed) + 1)); + return; + } + switch (found.spec) { + case CONVSPEC_NONE: + YOSYS_ABORT("Expected format conversion specifier for argument"); + break; + case CONVSPEC_ERROR: + YOSYS_ABORT("Found unsupported format conversion specifier"); + break; + case CONVSPEC_SIGNED_INT: + if constexpr (!std::is_convertible_v) { + YOSYS_ABORT("Expected type convertible to signed integer"); + } + *specs = found; + break; + case CONVSPEC_UNSIGNED_INT: + if constexpr (!std::is_convertible_v) { + YOSYS_ABORT("Expected type convertible to unsigned integer"); + } + *specs = found; + break; + case CONVSPEC_DOUBLE: + if constexpr (!std::is_convertible_v) { + YOSYS_ABORT("Expected type convertible to double"); + } + *specs = found; + break; + case CONVSPEC_CHAR_PTR: + if constexpr (!std::is_convertible_v && + !std::is_convertible_v && + !std::is_convertible_v) { + YOSYS_ABORT("Expected type convertible to char *"); + } + *specs = found; + break; + case CONVSPEC_VOID_PTR: + if constexpr (!std::is_convertible_v) { + YOSYS_ABORT("Expected pointer type"); + } + *specs = found; + break; + } + check_format(fmt, found.end, has_escapes, specs + 1, DynamicIntCount::NONE); +} + +// Emit the string representation of `arg` that has been converted to a `long long'. +void format_emit_long_long(std::string &result, std::string_view spec, int *dynamic_ints, + DynamicIntCount num_dynamic_ints, long long arg); + +// Emit the string representation of `arg` that has been converted to a `unsigned long long'. +void format_emit_unsigned_long_long(std::string &result, std::string_view spec, int *dynamic_ints, + DynamicIntCount num_dynamic_ints, unsigned long long arg); + +// Emit the string representation of `arg` that has been converted to a `double'. +void format_emit_double(std::string &result, std::string_view spec, int *dynamic_ints, + DynamicIntCount num_dynamic_ints, double arg); + +// Emit the string representation of `arg` that has been converted to a `const char*'. +void format_emit_char_ptr(std::string &result, std::string_view spec, int *dynamic_ints, + DynamicIntCount num_dynamic_ints, const char *arg); + +// Emit the string representation of `arg` that has been converted to a `std::string'. +void format_emit_string(std::string &result, std::string_view spec, int *dynamic_ints, + DynamicIntCount num_dynamic_ints, const std::string &arg); + +// Emit the string representation of `arg` that has been converted to a `std::string_view'. +void format_emit_string_view(std::string &result, std::string_view spec, int *dynamic_ints, + DynamicIntCount num_dynamic_ints, std::string_view arg); + +// Emit the string representation of `arg` that has been converted to a `double'. +void format_emit_void_ptr(std::string &result, std::string_view spec, int *dynamic_ints, + DynamicIntCount num_dynamic_ints, const void *arg); + +// Emit the string representation of `arg` according to the given `FoundFormatSpec`, +// appending it to `result`. +template +inline void format_emit_one(std::string &result, std::string_view fmt, const FoundFormatSpec &ffspec, + int *dynamic_ints, const Arg& arg) +{ + std::string_view spec = fmt.substr(ffspec.start, ffspec.end - ffspec.start); + DynamicIntCount num_dynamic_ints = ffspec.num_dynamic_ints; + switch (ffspec.spec) { + case CONVSPEC_SIGNED_INT: + if constexpr (std::is_convertible_v) { + long long s = arg; + format_emit_long_long(result, spec, dynamic_ints, num_dynamic_ints, s); + return; + } + break; + case CONVSPEC_UNSIGNED_INT: + if constexpr (std::is_convertible_v) { + unsigned long long s = arg; + format_emit_unsigned_long_long(result, spec, dynamic_ints, num_dynamic_ints, s); + return; + } + break; + case CONVSPEC_DOUBLE: + if constexpr (std::is_convertible_v) { + double s = arg; + format_emit_double(result, spec, dynamic_ints, num_dynamic_ints, s); + return; + } + break; + case CONVSPEC_CHAR_PTR: + if constexpr (std::is_convertible_v) { + const char *s = arg; + format_emit_char_ptr(result, spec, dynamic_ints, num_dynamic_ints, s); + return; + } + if constexpr (std::is_convertible_v) { + const std::string &s = arg; + format_emit_string(result, spec, dynamic_ints, num_dynamic_ints, s); + return; + } + if constexpr (std::is_convertible_v) { + const std::string_view &s = arg; + format_emit_string_view(result, spec, dynamic_ints, num_dynamic_ints, s); + return; + } + break; + case CONVSPEC_VOID_PTR: + if constexpr (std::is_convertible_v) { + const void *s = arg; + format_emit_void_ptr(result, spec, dynamic_ints, num_dynamic_ints, s); + return; + } + break; + default: + break; + } + YOSYS_ABORT("Internal error"); +} + +// Append the format string `fmt` to `result`, assuming there are no format conversion +// specifiers other than "%%" and therefore no arguments. Unescape "%%". +void format_emit_unescaped(std::string &result, std::string_view fmt); +std::string unescape_format_string(std::string_view fmt); + +inline void format_emit(std::string &result, std::string_view fmt, int fmt_start, + bool has_escapes, const FoundFormatSpec*, int*, DynamicIntCount) +{ + fmt = fmt.substr(fmt_start); + if (has_escapes) { + format_emit_unescaped(result, fmt); + } else { + result += fmt; + } +} +// Format `args` according to `fmt` (starting at `fmt_start`) and `specs` and append to `result`. +// `num_dynamic_ints` in `dynamic_ints[]` have already been collected to provide as +// dynamic width/precision args for the next format conversion specifier. +template +inline void format_emit(std::string &result, std::string_view fmt, int fmt_start, bool has_escapes, + const FoundFormatSpec* specs, int *dynamic_ints, DynamicIntCount num_dynamic_ints, + const Arg &arg, const Args &... args) +{ + if (specs->num_dynamic_ints > num_dynamic_ints) { + // Collect another int for the dynamic width precision/args + // for the next format conversion specifier. + if constexpr (std::is_convertible_v) { + dynamic_ints[static_cast(num_dynamic_ints)] = arg; + } else { + YOSYS_ABORT("Internal error"); + } + format_emit(result, fmt, fmt_start, has_escapes, specs, dynamic_ints, + static_cast(static_cast(num_dynamic_ints) + 1), args...); + return; + } + std::string_view str = fmt.substr(fmt_start, specs->start - fmt_start); + if (has_escapes) { + format_emit_unescaped(result, str); + } else { + result += str; + } + format_emit_one(result, fmt, *specs, dynamic_ints, arg); + format_emit(result, fmt, specs->end, has_escapes, specs + 1, dynamic_ints, DynamicIntCount::NONE, args...); +} + +template +inline std::string format_emit_toplevel(std::string_view fmt, bool has_escapes, const FoundFormatSpec* specs, const Args &... args) +{ + std::string result; + int dynamic_ints[2] = { 0, 0 }; + format_emit(result, fmt, 0, has_escapes, specs, dynamic_ints, DynamicIntCount::NONE, args...); + return result; +} +template <> +inline std::string format_emit_toplevel(std::string_view fmt, bool has_escapes, const FoundFormatSpec*) +{ + if (!has_escapes) { + return std::string(fmt); + } + return unescape_format_string(fmt); +} + +// This class parses format strings to build a list of `FoundFormatSpecs` in `specs`. +// When the compiler supports `consteval` (C++20), this parsing happens at compile time and +// type errors will be reported at compile time. Otherwise the parsing happens at +// runtime and type errors will trigger an `abort()` at runtime. +template +class FmtString +{ +public: + // Implicit conversion from const char * means that users can pass + // C string constants which are automatically parsed. + YOSYS_CONSTEVAL FmtString(const char *p) : fmt(p) + { + check_format(fmt, 0, &has_escapes, specs, DynamicIntCount::NONE); + } + std::string format(const Args &... args) + { + return format_emit_toplevel(fmt, has_escapes, specs, args...); + } +private: + std::string_view fmt; + bool has_escapes = false; + FoundFormatSpec specs[sizeof...(Args)] = {}; +}; + +template struct WrapType { using type = T; }; +template using TypeIdentity = typename WrapType::type; + +template +inline std::string stringf(FmtString...> fmt, Args... args) +{ + return fmt.format(args...); } int readsome(std::istream &f, char *s, int n); diff --git a/kernel/yosys_common.h b/kernel/yosys_common.h index e84676bc0..ecc8ce623 100644 --- a/kernel/yosys_common.h +++ b/kernel/yosys_common.h @@ -134,6 +134,15 @@ # define YS_COLD #endif +#ifdef __cpp_consteval +#define YOSYS_CONSTEVAL consteval +#else +// If we can't use consteval we can at least make it constexpr. +#define YOSYS_CONSTEVAL constexpr +#endif + +#define YOSYS_ABORT(s) abort() + #include "kernel/io.h" YOSYS_NAMESPACE_BEGIN