3
0
Fork 0
mirror of https://github.com/YosysHQ/yosys synced 2025-08-03 18:00:24 +00:00
yosys/kernel/io.cc
Robert O'Callahan 6ee3cd8ffd 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.
2025-07-29 05:29:33 +00:00

536 lines
13 KiB
C++

#include "kernel/yosys_common.h"
#include "kernel/log.h"
#include <iostream>
#include <string>
#if !defined(WIN32)
#include <dirent.h>
#include <unistd.h>
#else
#include <io.h>
#endif
YOSYS_NAMESPACE_BEGIN
// Set of utilities for handling files
int readsome(std::istream &f, char *s, int n)
{
int rc = int(f.readsome(s, n));
// f.readsome() sometimes returns 0 on a non-empty stream..
if (rc == 0) {
int c = f.get();
if (c != EOF) {
*s = c;
rc = 1;
}
}
return rc;
}
std::string next_token(std::string &text, const char *sep, bool long_strings)
{
size_t pos_begin = text.find_first_not_of(sep);
if (pos_begin == std::string::npos)
pos_begin = text.size();
if (long_strings && pos_begin != text.size() && text[pos_begin] == '"') {
std::string sep_string = sep;
for (size_t i = pos_begin+1; i < text.size(); i++) {
if (text[i] == '"' && (i+1 == text.size() || sep_string.find(text[i+1]) != std::string::npos)) {
std::string token = text.substr(pos_begin, i-pos_begin+1);
text = text.substr(i+1);
return token;
}
if (i+1 < text.size() && text[i] == '"' && text[i+1] == ';' && (i+2 == text.size() || sep_string.find(text[i+2]) != std::string::npos)) {
std::string token = text.substr(pos_begin, i-pos_begin+1);
text = text.substr(i+2);
return token + ";";
}
}
}
size_t pos_end = text.find_first_of(sep, pos_begin);
if (pos_end == std::string::npos)
pos_end = text.size();
std::string token = text.substr(pos_begin, pos_end-pos_begin);
text = text.substr(pos_end);
return token;
}
std::vector<std::string> split_tokens(const std::string &text, const char *sep)
{
std::vector<std::string> tokens;
std::string current_token;
for (char c : text) {
if (strchr(sep, c)) {
if (!current_token.empty()) {
tokens.push_back(current_token);
current_token.clear();
}
} else
current_token += c;
}
if (!current_token.empty()) {
tokens.push_back(current_token);
current_token.clear();
}
return tokens;
}
// this is very similar to fnmatch(). the exact rules used by this
// function are:
//
// ? matches any character except
// * matches any sequence of characters
// [...] matches any of the characters in the list
// [!..] matches any of the characters not in the list
//
// a backslash may be used to escape the next characters in the
// pattern. each special character can also simply match itself.
//
bool patmatch(const char *pattern, const char *string)
{
if (*pattern == 0)
return *string == 0;
if (*pattern == '\\') {
if (pattern[1] == string[0] && patmatch(pattern+2, string+1))
return true;
}
if (*pattern == '?') {
if (*string == 0)
return false;
return patmatch(pattern+1, string+1);
}
if (*pattern == '*') {
while (*string) {
if (patmatch(pattern+1, string++))
return true;
}
return pattern[1] == 0;
}
if (*pattern == '[') {
bool found_match = false;
bool inverted_list = pattern[1] == '!';
const char *p = pattern + (inverted_list ? 1 : 0);
while (*++p) {
if (*p == ']') {
if (found_match != inverted_list && patmatch(p+1, string+1))
return true;
break;
}
if (*p == '\\') {
if (*++p == *string)
found_match = true;
} else
if (*p == *string)
found_match = true;
}
}
if (*pattern == *string)
return patmatch(pattern+1, string+1);
return false;
}
std::string get_base_tmpdir()
{
static std::string tmpdir;
if (!tmpdir.empty()) {
return tmpdir;
}
#if defined(_WIN32)
# ifdef __MINGW32__
char longpath[MAX_PATH + 1];
char shortpath[MAX_PATH + 1];
# else
WCHAR longpath[MAX_PATH + 1];
TCHAR shortpath[MAX_PATH + 1];
# endif
if (!GetTempPath(MAX_PATH+1, longpath))
log_error("GetTempPath() failed.\n");
if (!GetShortPathName(longpath, shortpath, MAX_PATH + 1))
log_error("GetShortPathName() failed.\n");
for (int i = 0; shortpath[i]; i++)
tmpdir += char(shortpath[i]);
#else
char * var = std::getenv("TMPDIR");
if (var && strlen(var)!=0) {
tmpdir.assign(var);
// We return the directory name without the trailing '/'
while (!tmpdir.empty() && (tmpdir.back() == '/')) {
tmpdir.pop_back();
}
} else {
tmpdir.assign("/tmp");
}
#endif
return tmpdir;
}
std::string make_temp_file(std::string template_str)
{
size_t pos = template_str.rfind("XXXXXX");
log_assert(pos != std::string::npos);
#if defined(__wasm)
static size_t index = 0;
template_str.replace(pos, 6, stringf("%06zu", index++));
#elif defined(_WIN32)
#ifndef YOSYS_WIN32_UNIX_DIR
std::replace(template_str.begin(), template_str.end(), '/', '\\');
#endif
while (1) {
for (int i = 0; i < 6; i++) {
static std::string y = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
static uint32_t x = 314159265 ^ uint32_t(time(NULL));
x ^= x << 13, x ^= x >> 17, x ^= x << 5;
template_str[pos+i] = y[x % y.size()];
}
if (_access(template_str.c_str(), 0) != 0)
break;
}
#else
int suffixlen = template_str.size() - pos - 6;
char *p = strdup(template_str.c_str());
close(mkstemps(p, suffixlen));
template_str = p;
free(p);
#endif
return template_str;
}
std::string make_temp_dir(std::string template_str)
{
#if defined(_WIN32)
template_str = make_temp_file(template_str);
mkdir(template_str.c_str());
return template_str;
#elif defined(__wasm)
template_str = make_temp_file(template_str);
mkdir(template_str.c_str(), 0777);
return template_str;
#else
# ifndef NDEBUG
size_t pos = template_str.rfind("XXXXXX");
log_assert(pos != std::string::npos);
int suffixlen = template_str.size() - pos - 6;
log_assert(suffixlen == 0);
# endif
char *p = strdup(template_str.c_str());
log_assert(p);
char *res = mkdtemp(p);
if (!res)
log_error("mkdtemp failed for '%s': %s [Error %d]\n",
p, strerror(errno), errno);
template_str = p;
free(p);
return template_str;
#endif
}
bool check_is_directory(const std::string& dirname)
{
#if defined(_WIN32)
struct _stat info;
auto dirname_ = dirname;
/* On old versions of Visual Studio and current versions on MinGW,
_stat will fail if the path ends with a trailing slash. */
if (dirname.back() == '/' || dirname.back() == '\\') {
dirname_ = dirname.substr(0, dirname.length() - 1);
}
if (_stat(dirname_.c_str(), &info) != 0)
{
return false;
}
return (info.st_mode & _S_IFDIR) != 0;
#else
struct stat info;
if (stat(dirname.c_str(), &info) != 0)
{
return false;
}
return (info.st_mode & S_IFDIR) != 0;
#endif
}
#ifdef _WIN32
bool check_accessible(const std::string& filename, bool)
{
return _access(filename.c_str(), 0) == 0;
}
#else
bool check_accessible(const std::string& filename, bool is_exec)
{
return access(filename.c_str(), is_exec ? X_OK : F_OK) == 0;
}
#endif
bool check_file_exists(const std::string& filename, bool is_exec)
{
return check_accessible(filename, is_exec) && !check_is_directory(filename);
}
bool check_directory_exists(const std::string& filename, bool is_exec)
{
return check_accessible(filename, is_exec) && check_is_directory(filename);
}
bool is_absolute_path(std::string filename)
{
#ifdef _WIN32
return filename[0] == '/' || filename[0] == '\\' || (filename[0] != 0 && filename[1] == ':');
#else
return filename[0] == '/';
#endif
}
void remove_directory(std::string dirname)
{
#ifdef _WIN32
run_command(stringf("rmdir /s /q \"%s\"", dirname.c_str()));
#else
struct stat stbuf;
struct dirent **namelist;
int n = scandir(dirname.c_str(), &namelist, nullptr, alphasort);
log_assert(n >= 0);
for (int i = 0; i < n; i++) {
if (strcmp(namelist[i]->d_name, ".") && strcmp(namelist[i]->d_name, "..")) {
std::string buffer = stringf("%s/%s", dirname.c_str(), namelist[i]->d_name);
if (!stat(buffer.c_str(), &stbuf) && S_ISREG(stbuf.st_mode)) {
remove(buffer.c_str());
} else
remove_directory(buffer);
}
free(namelist[i]);
}
free(namelist);
rmdir(dirname.c_str());
#endif
}
bool create_directory(const std::string& dirname)
{
#if defined(_WIN32)
int ret = _mkdir(dirname.c_str());
#else
mode_t mode = 0755;
int ret = mkdir(dirname.c_str(), mode);
#endif
if (ret == 0)
return true;
switch (errno)
{
case ENOENT:
// parent didn't exist, try to create it
{
std::string::size_type pos = dirname.find_last_of('/');
if (pos == std::string::npos)
#if defined(_WIN32)
pos = dirname.find_last_of('\\');
if (pos == std::string::npos)
#endif
return false;
if (!create_directory( dirname.substr(0, pos) ))
return false;
}
// now, try to create again
#if defined(_WIN32)
return 0 == _mkdir(dirname.c_str());
#else
return 0 == mkdir(dirname.c_str(), mode);
#endif
case EEXIST:
// done!
return check_directory_exists(dirname);
default:
return false;
}
}
std::string escape_filename_spaces(const std::string& filename)
{
std::string out;
out.reserve(filename.size());
for (auto c : filename)
{
if (c == ' ')
out += "\\ ";
else
out.push_back(c);
}
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