From 119e998f120832ca7e5d61f434cc5efd37fa5197 Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Tue, 1 Apr 2025 13:48:44 +0200 Subject: [PATCH 1/2] read_liberty: Faster input handling for the liberty lexer The lexer for liberty files was using istream's `get` and `unget` which are notorious for bad performance and that showed up during profiling. This replaces the direct `istream` use with a custom LibertyInputStream that does its own buffering to provide `get` and `unget` that behave the same way but are implemented with a fast path that is easy to inline and optimize. --- kernel/yosys_common.h | 6 +++++ passes/techmap/libparse.cc | 45 ++++++++++++++++++++++++++++++++++++++ passes/techmap/libparse.h | 33 +++++++++++++++++++++++++++- 3 files changed, 83 insertions(+), 1 deletion(-) diff --git a/kernel/yosys_common.h b/kernel/yosys_common.h index a68539ce1..6fadf788f 100644 --- a/kernel/yosys_common.h +++ b/kernel/yosys_common.h @@ -128,6 +128,12 @@ # error "C++17 or later compatible compiler is required" #endif +#if defined(__has_cpp_attribute) && __has_cpp_attribute(gnu::cold) +# define YS_COLD [[gnu::cold]] +#else +# define YS_COLD +#endif + #include "kernel/io.h" YOSYS_NAMESPACE_BEGIN diff --git a/passes/techmap/libparse.cc b/passes/techmap/libparse.cc index 06dd6288e..dbf191080 100644 --- a/passes/techmap/libparse.cc +++ b/passes/techmap/libparse.cc @@ -32,6 +32,51 @@ using namespace Yosys; +bool LibertyInputStream::extend_buffer_once() +{ + if (eof) + return false; + + // To support unget we leave the last already read character in the buffer + if (buf_pos > 1) { + size_t move_pos = buf_pos - 1; + memmove(buffer.data(), buffer.data() + move_pos, buf_end - move_pos); + buf_pos -= move_pos; + buf_end -= move_pos; + } + + const size_t chunk_size = 4096; + if (buffer.size() < buf_end + chunk_size) { + buffer.resize(buf_end + chunk_size); + } + + size_t read_size = f.rdbuf()->sgetn(buffer.data() + buf_end, chunk_size); + buf_end += read_size; + if (read_size < chunk_size) + eof = true; + return read_size != 0; +} + +bool LibertyInputStream::extend_buffer_at_least(size_t size) { + while (buffered_size() < size) { + if (!extend_buffer_once()) + return false; + } + return true; +} + +int LibertyInputStream::get_cold() +{ + if (buf_pos == buf_end) { + if (!extend_buffer_at_least()) + return EOF; + } + + int c = buffer[buf_pos]; + buf_pos += 1; + return c; +} + LibertyAst::~LibertyAst() { for (auto child : children) diff --git a/passes/techmap/libparse.h b/passes/techmap/libparse.h index 16808fc58..eb73e296d 100644 --- a/passes/techmap/libparse.h +++ b/passes/techmap/libparse.h @@ -90,12 +90,43 @@ namespace Yosys bool eval(dict& values); }; + class LibertyInputStream { + std::istream &f; + std::vector buffer; + size_t buf_pos = 0; + size_t buf_end = 0; + bool eof = false; + + bool extend_buffer_once(); + bool extend_buffer_at_least(size_t size = 1); + + YS_COLD int get_cold(); + + public: + LibertyInputStream(std::istream &f) : f(f) {} + + size_t buffered_size() { return buf_end - buf_pos; } + const char *buffered_data() { return buffer.data() + buf_pos; } + + int get() { + if (buf_pos == buf_end) + return get_cold(); + int c = buffer[buf_pos]; + buf_pos += 1; + return c; + } + + void unget() { + buf_pos -= 1; + } + }; + class LibertyMergedCells; class LibertyParser { friend class LibertyMergedCells; private: - std::istream &f; + LibertyInputStream f; int line; /* lexer return values: From bc01468c7545b2362a52b48f11fa0e5cccb4e75a Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Tue, 1 Apr 2025 13:50:29 +0200 Subject: [PATCH 2/2] read_liberty: Faster std::string construction in the liberty lexer This extends the `LibertyInputStream` added in the previous commit to allow arbitrary lookahead. Then this uses the lookahead to find the total length of the token within the input buffer, instead of consuming the token byte by byte while appending to a std::string. Constructing the std::string with the total length is known avoids any reallocations from growing std::string's buffer. --- passes/techmap/libparse.cc | 55 ++++++++++++++++++++++++-------------- passes/techmap/libparse.h | 11 ++++++++ 2 files changed, 46 insertions(+), 20 deletions(-) diff --git a/passes/techmap/libparse.cc b/passes/techmap/libparse.cc index dbf191080..d3d5b7d57 100644 --- a/passes/techmap/libparse.cc +++ b/passes/techmap/libparse.cc @@ -77,6 +77,16 @@ int LibertyInputStream::get_cold() return c; } +int LibertyInputStream::peek_cold(size_t offset) +{ + if (buf_pos + offset >= buf_end) { + if (!extend_buffer_at_least(offset + 1)) + return EOF; + } + + return buffer[buf_pos + offset]; +} + LibertyAst::~LibertyAst() { for (auto child : children) @@ -282,15 +292,19 @@ int LibertyParser::lexer(std::string &str) // search for identifiers, numbers, plus or minus. if (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '_' || c == '-' || c == '+' || c == '.') { - str = static_cast(c); - while (1) { - c = f.get(); + f.unget(); + size_t i = 1; + while (true) { + c = f.peek(i); if (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '_' || c == '-' || c == '+' || c == '.') - str += c; + i += 1; else break; } - f.unget(); + str.clear(); + str.append(f.buffered_data(), f.buffered_data() + i); + f.consume(i); + if (str == "+" || str == "-") { /* Single operator is not an identifier */ // fprintf(stderr, "LEX: char >>%s<<\n", str.c_str()); @@ -305,23 +319,24 @@ int LibertyParser::lexer(std::string &str) // if it wasn't an identifer, number of array range, // maybe it's a string? if (c == '"') { - str = ""; -#ifdef FILTERLIB - str += c; -#endif - while (1) { - c = f.get(); - if (c == '\n') - line++; - if (c == '"') { -#ifdef FILTERLIB - str += c; -#endif + size_t i = 0; + while (true) { + c = f.peek(i); + line += (c == '\n'); + if (c != '"') + i += 1; + else break; - } - str += c; } - // fprintf(stderr, "LEX: string >>%s<<\n", str.c_str()); + str.clear(); +#ifdef FILTERLIB + f.unget(); + str.append(f.buffered_data(), f.buffered_data() + i + 2); + f.consume(i + 2); +#else + str.append(f.buffered_data(), f.buffered_data() + i); + f.consume(i + 1); +#endif return 'v'; } diff --git a/passes/techmap/libparse.h b/passes/techmap/libparse.h index eb73e296d..1fcaaebee 100644 --- a/passes/techmap/libparse.h +++ b/passes/techmap/libparse.h @@ -101,6 +101,7 @@ namespace Yosys bool extend_buffer_at_least(size_t size = 1); YS_COLD int get_cold(); + YS_COLD int peek_cold(size_t offset); public: LibertyInputStream(std::istream &f) : f(f) {} @@ -116,6 +117,16 @@ namespace Yosys return c; } + int peek(size_t offset = 0) { + if (buf_pos + offset >= buf_end) + return peek_cold(offset); + return buffer[buf_pos + offset]; + } + + void consume(size_t n = 1) { + buf_pos += n; + } + void unget() { buf_pos -= 1; }