From e97243f173c824c630acf0798317144c0c26e05c Mon Sep 17 00:00:00 2001 From: Yu-Sheng Lin Date: Mon, 16 Mar 2026 02:20:32 +0800 Subject: [PATCH] Fix regression error and review comments --- include/fstcpp/fstcpp.h | 42 +- include/fstcpp/fstcpp_assertion.h | 16 +- include/fstcpp/fstcpp_stream_write_helper.h | 181 ++++---- include/fstcpp/fstcpp_variable_info.cpp | 21 +- include/fstcpp/fstcpp_variable_info.h | 125 ++--- include/fstcpp/fstcpp_writer.cpp | 490 ++++++++++---------- include/fstcpp/fstcpp_writer.h | 359 ++++---------- include/verilated_fst_c.cpp | 15 +- 8 files changed, 560 insertions(+), 689 deletions(-) diff --git a/include/fstcpp/fstcpp.h b/include/fstcpp/fstcpp.h index 6808b5139..5eb0c7414 100644 --- a/include/fstcpp/fstcpp.h +++ b/include/fstcpp/fstcpp.h @@ -10,7 +10,7 @@ #include #include #include -#include +#include // Other libraries' .h files. // Your project's .h files. @@ -23,11 +23,23 @@ namespace fst { typedef uint32_t Handle; typedef uint32_t EnumHandle; -using string_view_pair = std::pair; +struct string_view_pair { + const char *m_data = nullptr; + size_t m_size = 0; + + // implicit conversion from const char*, std::string, std::string_view + string_view_pair(const char *data) + : m_data{data}, m_size{data == nullptr ? 0 : std::strlen(data)} {} + string_view_pair(const char *data, size_t size) : m_data{data}, m_size{size} {} + string_view_pair(const std::string &s) : m_data{s.c_str()}, m_size{s.size()} {} +#if __cplusplus >= 201703L + string_view_pair(std::string_view s) : m_data{s.data()}, m_size{s.size()} {} +#endif +}; [[maybe_unused]] static inline string_view_pair make_string_view_pair(const char *data) { - if (not data) { + if (!data) { return {nullptr, 0}; } return {data, std::strlen(data)}; @@ -217,19 +229,19 @@ struct Hierarchy { }; struct Header { - uint64_t start_time = uint64_t(-1); - uint64_t end_time = 0; - int64_t timezero = 0; + uint64_t m_start_time{uint64_t(-1)}; + uint64_t m_end_time{0}; + int64_t m_timezero{0}; // Match the original fstapi.c. Just for information, not used in FST. - uint64_t writer_memory_use = 1ull << 27; - uint64_t num_scopes = 0; - uint64_t num_vars = 0; // #CreateVar calls, including aliases - uint64_t num_handles = 0; // #unique handles, excluding aliases, shall be <= num_vars - uint64_t num_value_change_data_blocks = 0; - char writer[128]{}; - char date[26]{}; - FileType filetype = FileType::VERILOG; - int8_t timescale = -9; + uint64_t m_writer_memory_use{1ull << 27}; + uint64_t m_num_scopes{0}; + uint64_t m_num_vars{0}; // #CreateVar calls, including aliases + uint64_t m_num_handles{0}; // #unique handles, excluding aliases, shall be <= m_num_vars + uint64_t m_num_value_change_data_blocks{0}; + char m_writer[128]{}; + char m_date[26]{}; + FileType m_filetype{FileType::VERILOG}; + int8_t m_timescale{-9}; }; static constexpr uint64_t kInvalidTime = uint64_t(-1); diff --git a/include/fstcpp/fstcpp_assertion.h b/include/fstcpp/fstcpp_assertion.h index b8567d116..1f7265b13 100644 --- a/include/fstcpp/fstcpp_assertion.h +++ b/include/fstcpp/fstcpp_assertion.h @@ -6,9 +6,9 @@ // direct include // C system headers // C++ standard library headers +#include #include #include -#include // Other libraries' .h files. // Your project's .h files. @@ -18,7 +18,7 @@ oss << "FST_CHECK failed: " #a; \ const auto e = oss.str(); \ std::cerr << e << std::endl; \ - throw std::runtime_error(e); \ + std::abort(); \ } #define FST_CHECK_EQ(a, b) \ @@ -28,7 +28,7 @@ oss << " (" << (a) << " vs. " << (b) << ")"; \ const auto e = oss.str(); \ std::cerr << e << std::endl; \ - throw std::runtime_error(e); \ + std::abort(); \ } #define FST_CHECK_NE(a, b) \ @@ -38,7 +38,7 @@ oss << " (" << (a) << " vs. " << (b) << ")"; \ const auto e = oss.str(); \ std::cerr << e << std::endl; \ - throw std::runtime_error(e); \ + std::abort(); \ } #define FST_CHECK_GT(a, b) \ @@ -48,7 +48,7 @@ oss << " (" << (a) << " vs. " << (b) << ")"; \ const auto e = oss.str(); \ std::cerr << e << std::endl; \ - throw std::runtime_error(e); \ + std::abort(); \ } #define FST_CHECK_GE(a, b) \ @@ -58,7 +58,7 @@ oss << " (" << (a) << " vs. " << (b) << ")"; \ const auto e = oss.str(); \ std::cerr << e << std::endl; \ - throw std::runtime_error(e); \ + std::abort(); \ } #define FST_CHECK_LT(a, b) \ @@ -68,7 +68,7 @@ oss << " (" << (a) << " vs. " << (b) << ")"; \ const auto e = oss.str(); \ std::cerr << e << std::endl; \ - throw std::runtime_error(e); \ + std::abort(); \ } #define FST_CHECK_LE(a, b) \ @@ -78,7 +78,7 @@ oss << " (" << (a) << " vs. " << (b) << ")"; \ const auto e = oss.str(); \ std::cerr << e << std::endl; \ - throw std::runtime_error(e); \ + std::abort(); \ } // We turn on all DCHECKs to CHECKs temporarily for better safety. diff --git a/include/fstcpp/fstcpp_stream_write_helper.h b/include/fstcpp/fstcpp_stream_write_helper.h index 70621e0a1..e3b9158ba 100644 --- a/include/fstcpp/fstcpp_stream_write_helper.h +++ b/include/fstcpp/fstcpp_stream_write_helper.h @@ -6,10 +6,12 @@ #pragma once // direct include // C system headers +#ifdef _MSC_VER +# include +#endif // C++ standard library headers #include #include -#include #include // Other libraries' .h files. // Your project's .h files. @@ -26,10 +28,27 @@ namespace platform { // clang-format off template U to_big_endian(U u) { return u; } #else +#if defined(__GNUC__) || defined(__clang__) template U to_big_endian(U u, std::integral_constant) { return u; } template U to_big_endian(U u, std::integral_constant) { return __builtin_bswap16(u); } template U to_big_endian(U u, std::integral_constant) { return __builtin_bswap32(u); } template U to_big_endian(U u, std::integral_constant) { return __builtin_bswap64(u); } +#elif defined(_MSC_VER) // MSVC +template U to_big_endian(U u, std::integral_constant) { return u; } +template U to_big_endian(U u, std::integral_constant) { return _byteswap_ushort(u); } +template U to_big_endian(U u, std::integral_constant) { return _byteswap_ulong(u); } +template U to_big_endian(U u, std::integral_constant) { return _byteswap_uint64(u); } +#else +template U to_big_endian(U u, std::integral_constant) { + U ret{ 0 }; + for (size_t i = 0; i < S; ++i) { + ret |= u & 0xff; + ret <<= 8; + u >>= 8; + } + return ret; +} +#endif // clang-format on template U to_big_endian(U u) { @@ -40,17 +59,17 @@ U to_big_endian(U u) { } // namespace platform struct StreamWriteHelper { - std::ostream *os; + std::ostream *m_os{nullptr}; - StreamWriteHelper(std::ostream &os_) : os(&os_) {} - StreamWriteHelper(std::ostream *os_) : os(os_) {} + StreamWriteHelper(std::ostream &os_) : m_os{&os_} {} + StreamWriteHelper(std::ostream *os_) : m_os{os_} {} // Write the entire uint, big-endian // We do not provide little-endian version since FST only uses big-endian template StreamWriteHelper &writeUInt(U u) { u = platform::to_big_endian(u); - os->write(reinterpret_cast(&u), sizeof(u)); + m_os->write(reinterpret_cast(&u), sizeof(u)); return *this; } @@ -64,35 +83,35 @@ struct StreamWriteHelper { u <<= sizeof(u) * 8 - bitwidth; // Write the first (bitwidth+7)/8 bytes u = platform::to_big_endian(u); - os->write(reinterpret_cast(&u), (bitwidth + 7) / 8); + m_os->write(reinterpret_cast(&u), (bitwidth + 7) / 8); return *this; } StreamWriteHelper &writeLEB128(uint64_t v) { // Just reuse the logic from fstapi.c, is there a better way? - uint64_t nxt; - unsigned char buf[10]; /* ceil(64/7) = 10 */ - unsigned char *pnt = buf; - int len; + uint64_t nxt{0}; + unsigned char buf[10]{}; /* ceil(64/7) = 10 */ + unsigned char *pnt{buf}; + int len{0}; while ((nxt = v >> 7)) { *(pnt++) = ((unsigned char)v) | 0x80; v = nxt; } *(pnt++) = (unsigned char)v; - len = pnt - buf; - os->write(reinterpret_cast(buf), len); + len = static_cast(pnt - buf); + m_os->write(reinterpret_cast(buf), len); return *this; } StreamWriteHelper &writeLEB128Signed(int64_t v) { // Just reuse the logic from fstapi.c, is there a better way? - unsigned char buf[15]; /* ceil(64/7) = 10 + sign byte padded way up */ - unsigned char byt; - unsigned char *pnt = buf; - int more = 1; - int len; + unsigned char buf[15]{}; /* ceil(64/7) = 10 + sign byte padded way up */ + unsigned char byt{0}; + unsigned char *pnt{buf}; + int more{1}; + int len{0}; do { - byt = v | 0x80; + byt = static_cast(v | 0x80); v >>= 7; if (((!v) && (!(byt & 0x40))) || ((v == -1) && (byt & 0x40))) { @@ -102,15 +121,15 @@ struct StreamWriteHelper { *(pnt++) = byt; } while (more); - len = pnt - buf; - os->write(reinterpret_cast(buf), len); + len = static_cast(pnt - buf); + m_os->write(reinterpret_cast(buf), len); return *this; } template StreamWriteHelper &writeFloat(F f) { // Always write in native endianness - os->write(reinterpret_cast(&f), sizeof(f)); + m_os->write(reinterpret_cast(&f), sizeof(f)); return *this; } @@ -126,13 +145,13 @@ struct StreamWriteHelper { // Write the string, non-null-terminated StreamWriteHelper &writeString(const fst::string_view_pair str) { - os->write(str.first, str.second); + m_os->write(str.m_data, str.m_size); return *this; } // Write the string, null-terminated StreamWriteHelper &writeString0(const fst::string_view_pair str) { - os->write(str.first, str.second).put('\0'); + m_os->write(str.m_data, str.m_size).put('\0'); return *this; } StreamWriteHelper &writeString(const std::string &str) { @@ -143,33 +162,33 @@ struct StreamWriteHelper { } StreamWriteHelper &write(const char *ptr, size_t size) { - os->write(ptr, size); + m_os->write(ptr, size); return *this; } StreamWriteHelper &write(const uint8_t *ptr, size_t size) { - os->write(reinterpret_cast(ptr), size); + m_os->write(reinterpret_cast(ptr), size); return *this; } StreamWriteHelper &seek(std::streamoff pos, std::ios_base::seekdir dir) { - os->seekp(pos, dir); + m_os->seekp(pos, dir); return *this; } StreamWriteHelper &fill(char fill_char, size_t size) { if (size > 32) { // optimize large fills - constexpr unsigned kChunkSize = 16; - char buf[kChunkSize]; - std::memset(buf, fill_char, kChunkSize); - for (size_t i = 0; i < size / kChunkSize; ++i) { - os->write(buf, kChunkSize); + constexpr unsigned s_kChunkSize = 16; + char buf[s_kChunkSize]{}; + std::memset(buf, fill_char, s_kChunkSize); + for (size_t i{0}; i < size / s_kChunkSize; ++i) { + m_os->write(buf, s_kChunkSize); } - size %= kChunkSize; + size %= s_kChunkSize; } - for (size_t i = 0; i < size; ++i) { - os->put(fill_char); + for (size_t i{0}; i < size; ++i) { + m_os->put(fill_char); } return *this; } @@ -197,41 +216,41 @@ struct StreamWriteHelper { // to endOffset(), which is a common mistake. StreamWriteHelper &beginOffset(std::streamoff &pos) { - pos = os->tellp(); + pos = m_os->tellp(); return *this; } StreamWriteHelper &endOffset(std::streamoff *diff) { // diff shall store previous position before calling this function - *diff = os->tellp() - *diff; + *diff = m_os->tellp() - *diff; return *this; } StreamWriteHelper &endOffset(std::streamoff *diff, std::streamoff pos) { - *diff = os->tellp() - pos; + *diff = m_os->tellp() - pos; return *this; } }; struct StreamVectorWriteHelper { - std::vector &vec; + std::vector &m_vec; - StreamVectorWriteHelper(std::vector &vec_) : vec(vec_) {} + StreamVectorWriteHelper(std::vector &vec_) : m_vec{vec_} {} template StreamVectorWriteHelper &write(T u) { const size_t s = sizeof(u); - vec.resize(vec.size() + s); - std::memcpy(vec.data() + vec.size() - s, &u, s); + m_vec.resize(m_vec.size() + s); + std::memcpy(m_vec.data() + m_vec.size() - s, &u, s); return *this; } template StreamVectorWriteHelper &fill(T u, size_t count) { const size_t s = sizeof(u) * count; - vec.resize(vec.size() + s); - for (size_t i = 0; i < count; ++i) { - std::memcpy(vec.data() + vec.size() - s + i * sizeof(u), &u, sizeof(u)); + m_vec.resize(m_vec.size() + s); + for (size_t i{0}; i < count; ++i) { + std::memcpy(m_vec.data() + m_vec.size() - s + i * sizeof(u), &u, sizeof(u)); } return *this; } @@ -239,14 +258,14 @@ struct StreamVectorWriteHelper { template StreamVectorWriteHelper &write(T *u, size_t size) { const size_t s = sizeof(u) * size; - vec.resize(vec.size() + s); - std::memcpy(vec.data() + vec.size() - s, u, s); + m_vec.resize(m_vec.size() + s); + std::memcpy(m_vec.data() + m_vec.size() - s, u, s); return *this; } template StreamVectorWriteHelper &writeU8Enum(E e) { - vec.push_back(static_cast(e)); + m_vec.push_back(static_cast(e)); return *this; } @@ -256,8 +275,8 @@ struct StreamVectorWriteHelper { StreamVectorWriteHelper &writeUIntBE(U u) { u = platform::to_big_endian(u); const size_t s = sizeof(u); - vec.resize(vec.size() + s); - std::memcpy(vec.data() + vec.size() - s, &u, s); + m_vec.resize(m_vec.size() + s); + std::memcpy(m_vec.data() + m_vec.size() - s, &u, s); return *this; } @@ -272,39 +291,39 @@ struct StreamVectorWriteHelper { // Write the first (bitwidth+7)/8 bytes u = platform::to_big_endian(u); const size_t s = (bitwidth + 7) / 8; - vec.resize(vec.size() + s); - std::memcpy(vec.data() + vec.size() - s, &u, s); + m_vec.resize(m_vec.size() + s); + std::memcpy(m_vec.data() + m_vec.size() - s, &u, s); return *this; } StreamVectorWriteHelper &writeLEB128(uint64_t v) { // Just reuse the logic from fstapi.c, is there a better way? - uint64_t nxt; - unsigned char buf[10]; /* ceil(64/7) = 10 */ - unsigned char *pnt = buf; - int len; + uint64_t nxt{0}; + unsigned char buf[10]{}; /* ceil(64/7) = 10 */ + unsigned char *pnt{buf}; + int len{0}; while ((nxt = v >> 7)) { *(pnt++) = ((unsigned char)v) | 0x80; v = nxt; } *(pnt++) = (unsigned char)v; - len = pnt - buf; + len = static_cast(pnt - buf); - const size_t cur = vec.size(); - vec.resize(cur + len); - std::memcpy(vec.data() + cur, buf, len); + const size_t cur = m_vec.size(); + m_vec.resize(cur + len); + std::memcpy(m_vec.data() + cur, buf, len); return *this; } StreamVectorWriteHelper &writeLEB128Signed(int64_t v) { // Just reuse the logic from fstapi.c, is there a better way? - unsigned char buf[15]; /* ceil(64/7) = 10 + sign byte padded way up */ - unsigned char byt; - unsigned char *pnt = buf; - int more = 1; - int len; + unsigned char buf[15]{}; /* ceil(64/7) = 10 + sign byte padded way up */ + unsigned char byt{0}; + unsigned char *pnt{buf}; + int more{1}; + int len{0}; do { - byt = v | 0x80; + byt = static_cast(v | 0x80); v >>= 7; if (((!v) && (!(byt & 0x40))) || ((v == -1) && (byt & 0x40))) { @@ -314,11 +333,11 @@ struct StreamVectorWriteHelper { *(pnt++) = byt; } while (more); - len = pnt - buf; + len = static_cast(pnt - buf); - const size_t cur = vec.size(); - vec.resize(cur + len); - std::memcpy(vec.data() + cur, buf, len); + const size_t cur = m_vec.size(); + m_vec.resize(cur + len); + std::memcpy(m_vec.data() + cur, buf, len); return *this; } @@ -334,25 +353,25 @@ struct StreamVectorWriteHelper { // Write the string, non-null-terminated StreamVectorWriteHelper &writeString(const fst::string_view_pair str) { - if (str.second != 0) { - const size_t len = str.second; - const size_t cur = vec.size(); - vec.resize(cur + len); - std::memcpy(vec.data() + cur, str.first, len); + if (str.m_size != 0) { + const size_t len = str.m_size; + const size_t cur = m_vec.size(); + m_vec.resize(cur + len); + std::memcpy(m_vec.data() + cur, str.m_data, len); } return *this; } // Write the string, null-terminated StreamVectorWriteHelper &writeString0(const fst::string_view_pair str) { - if (str.second != 0) { - const size_t len = str.second; - const size_t cur = vec.size(); - vec.resize(cur + len + 1); - std::memcpy(vec.data() + cur, str.first, len); - vec[cur + len] = '\0'; + if (str.m_size != 0) { + const size_t len = str.m_size; + const size_t cur = m_vec.size(); + m_vec.resize(cur + len + 1); + std::memcpy(m_vec.data() + cur, str.m_data, len); + m_vec[cur + len] = '\0'; } else { - vec.push_back('\0'); + m_vec.push_back('\0'); } return *this; } diff --git a/include/fstcpp/fstcpp_variable_info.cpp b/include/fstcpp/fstcpp_variable_info.cpp index d748c2e29..c085d8719 100644 --- a/include/fstcpp/fstcpp_variable_info.cpp +++ b/include/fstcpp/fstcpp_variable_info.cpp @@ -18,16 +18,21 @@ constexpr uint64_t VariableInfo::kCapacityBase; void VariableInfo::reallocate(uint64_t new_size) { // Allocate new memory - const uint32_t new_capacity_log2 = - std::max(platform::clog2(new_size), kCapacityBaseShift) - kCapacityBaseShift; - uint8_t *new_data = new uint8_t[kCapacityBase << new_capacity_log2]; + const uint32_t new_capacity_log2{ + std::max( + static_cast(platform::clog2(new_size)), + static_cast(kCapacityBaseShift) + ) - + static_cast(kCapacityBaseShift) + }; + uint8_t *new_data{new uint8_t[kCapacityBase << new_capacity_log2]}; // Copy old data to new memory - if (data != nullptr) { - const uint64_t old_size = size(); - std::copy_n(data, old_size, new_data); - delete[] data; + if (m_data != nullptr) { + const uint64_t old_size{size()}; + std::copy_n(m_data, old_size, new_data); + delete[] m_data; } - data = new_data; + m_data = new_data; capacity_log2(new_capacity_log2); } diff --git a/include/fstcpp/fstcpp_variable_info.h b/include/fstcpp/fstcpp_variable_info.h index a6d64fc8e..b91ab5103 100644 --- a/include/fstcpp/fstcpp_variable_info.h +++ b/include/fstcpp/fstcpp_variable_info.h @@ -7,6 +7,9 @@ // direct include #include "fstcpp/fstcpp.h" // C system headers +#ifdef _MSC_VER +# include +#endif // C++ standard library headers #include #include @@ -23,7 +26,21 @@ namespace platform { // Can be replaced with std::bit_width when C++20 is available inline uint64_t clog2(uint64_t x) { +#if defined(__GNUC__) || defined(__clang__) return 64 - __builtin_clzll(x - 1); +#elif defined(_MSC_VER) // MSVC + if (x <= 1) return 0; + unsigned long index; + _BitScanReverse64(&index, x - 1); + return static_cast(index + 1); +#else + uint64_t r = 0; + while (x > 1) { + x >>= 1; + r++; + } + return r; +#endif } inline constexpr uint32_t gen_mask_safe(unsigned width) { @@ -44,6 +61,10 @@ inline void write_field(uint32_t &dst, const uint32_t src, unsigned width, unsig } // namespace platform class VariableInfo final { +public: + static constexpr uint32_t kMaxSupportedBitwidth = 0x7fffff; + +private: static constexpr uint64_t kCapacityBaseShift = 5; static constexpr uint64_t kCapacityBase = 1 << kCapacityBaseShift; @@ -54,17 +75,19 @@ class VariableInfo final { // begin of data members // 1. 8B pointer (assume 64-bit architecture), its size can be: - // - 0 if data is nullptr - // - `kCapacityBase * pow(2, capacity_log2)` if data is not nullptr + // - 0 if m_data is nullptr + // - `kCapacityBase * pow(2, m_capacity_log2)` if m_data is not nullptr // - If we want more bits, we can use the `kCapacityBaseShift` LSB for other purposes. - uint8_t *data = nullptr; + uint8_t *m_data{nullptr}; // 2. 4B size. The same as vector.size(), but we only need 32b. - uint32_t size_ = 0; + uint32_t m_size{0}; // 3. 4B misc. Highly compacted information for max cache efficiency. // - 6b capacity_log2 // - 2b last_encoding_type // - 23b bitwidth // - 1b is_real + uint32_t m_misc{0}; + // end of data members // Note: optimization possibility (not implemented) // - real is always 64-bit double, so we can use 24 bits to encode @@ -82,42 +105,42 @@ class VariableInfo final { static constexpr uint32_t kLastEncodingTypeOffset = kBitwidthOffset + kBitwidthWidth; static constexpr uint32_t kCapacityLog2Offset = kLastEncodingTypeOffset + kLastEncodingTypeWidth; - uint32_t misc = 0; - // end of data members void capacity_log2(uint32_t capacity_log2_) { - platform::write_field(misc, capacity_log2_, kCapacityLog2Width, kCapacityLog2Offset); + platform::write_field(m_misc, capacity_log2_, kCapacityLog2Width, kCapacityLog2Offset); } uint32_t capacity() const { - if (data == nullptr) { + if (m_data == nullptr) { return 0; } - return kCapacityBase << platform::read_field(misc, kCapacityLog2Width, kCapacityLog2Offset); + return kCapacityBase << platform::read_field( + m_misc, kCapacityLog2Width, kCapacityLog2Offset + ); } - inline bool need_reallocate(uint64_t new_size) const { return capacity() < new_size; } + bool need_reallocate(uint64_t new_size) const { return capacity() < new_size; } // This function is cold, so we don't inline it void reallocate(uint64_t new_size); - inline void size(uint64_t s) { size_ = s; } + void size(uint64_t s) { m_size = static_cast(s); } public: - static constexpr uint32_t kMaxSupportedBitwidth = 0x7fffff; - inline uint64_t size() const { return size_; } - inline uint32_t bitwidth() const { - return platform::read_field(misc, kBitwidthWidth, kBitwidthOffset); + uint64_t size() const { return m_size; } + uint32_t bitwidth() const { + return platform::read_field(m_misc, kBitwidthWidth, kBitwidthOffset); } - inline bool is_real() const { - return bool(platform::read_field(misc, kIsRealWidth, kIsRealOffset)); - } - inline void last_written_encode_type(EncodingType encoding_) { + bool is_real() const { return bool(platform::read_field(m_misc, kIsRealWidth, kIsRealOffset)); } + void last_written_encode_type(EncodingType encoding_) { platform::write_field( - misc, static_cast(encoding_), kLastEncodingTypeWidth, kLastEncodingTypeOffset + m_misc, + static_cast(encoding_), + kLastEncodingTypeWidth, + kLastEncodingTypeOffset ); } - inline EncodingType last_written_encode_type() const { + EncodingType last_written_encode_type() const { return static_cast( - platform::read_field(misc, kLastEncodingTypeWidth, kLastEncodingTypeOffset) + platform::read_field(m_misc, kLastEncodingTypeWidth, kLastEncodingTypeOffset) ); } uint64_t last_written_bytes() const; @@ -135,11 +158,10 @@ public: } } VariableInfo(VariableInfo &&rhs) { - data = rhs.data; - rhs.data = nullptr; - misc = rhs.misc; - size_ = rhs.size_; - // rhs.misc = 0; + m_data = rhs.m_data; + rhs.m_data = nullptr; + m_misc = rhs.m_misc; + m_size = rhs.m_size; } uint32_t emitValueChange(uint64_t current_time_index, const uint64_t val); @@ -151,8 +173,8 @@ public: ); void keepOnlyTheLatestValue() { - const auto last_written_bytes_ = last_written_bytes(); - const auto data_ptr_ = data_ptr(); + const uint64_t last_written_bytes_ = last_written_bytes(); + uint8_t *data_ptr_ = data_ptr(); std::copy_n(data_ptr_ + size() - last_written_bytes_, last_written_bytes_, data_ptr_); size(last_written_bytes_); } @@ -172,7 +194,7 @@ public: size(new_size); } void add_size(size_t added_size) { resize(size() + added_size); } - uint8_t *data_ptr() { return data; } + uint8_t *data_ptr() { return m_data; } }; static_assert( sizeof(VariableInfo) != 12, @@ -294,7 +316,6 @@ public: void emitValueChange(uint64_t current_time_index, const uint64_t val) { auto wh = emitValueChangeCommonPart(current_time_index, EncodingType::BINARY); - std::cout << current_time_index << ": " << std::hex << val << std::endl; // Note, do not use write here since the uint64_t is // already bit_cast'ed from double wh.write(val); @@ -358,14 +379,12 @@ public: VariableInfoScalarInt(VariableInfo &info_) : info(info_) {} public: - inline size_t computeBytesNeeded(EncodingType encoding) const { + size_t computeBytesNeeded(EncodingType encoding) const { return kEmitTimeIndexAndEncodingSize + sizeof(T) * bitPerEncodedBit(encoding); } // The returning address points to the first byte of the value - inline EmitWriterHelper emitValueChangeCommonPart( - uint64_t current_time_index, EncodingType encoding - ) { + EmitWriterHelper emitValueChangeCommonPart(uint64_t current_time_index, EncodingType encoding) { if (current_time_index + 1 == 0) { // This is the first value change, we need to remove everything // and then add the new value @@ -548,16 +567,14 @@ public: VariableInfoLongInt(VariableInfo &info_) : info(info_) {} public: - inline size_t computeBytesNeeded(EncodingType encoding) const { + size_t computeBytesNeeded(EncodingType encoding) const { return ( kEmitTimeIndexAndEncodingSize + num_words() * sizeof(uint64_t) * bitPerEncodedBit(encoding) ); } - inline EmitWriterHelper emitValueChangeCommonPart( - uint64_t current_time_index, EncodingType encoding - ) { + EmitWriterHelper emitValueChangeCommonPart(uint64_t current_time_index, EncodingType encoding) { if (current_time_index + 1 == 0) { info.resize(0); } @@ -727,9 +744,9 @@ public: template auto VariableInfo::dispatchHelper(Callable &&callable, Args &&...args) const { - const auto bitwidth = this->bitwidth(); - const auto is_real = this->is_real(); - if (not is_real) { + const uint32_t bitwidth = this->bitwidth(); + const bool is_real = this->is_real(); + if (!is_real) { // Decision: the branch miss is too expensive for large design, so we only use 3 types of // int if (bitwidth <= 8) { @@ -737,12 +754,6 @@ auto VariableInfo::dispatchHelper(Callable &&callable, Args &&...args) const { detail::VariableInfoScalarInt(const_cast(*this)), std::forward(args)... ); - // } else if (bitwidth <= 16) { - // return - // callable(detail::VariableInfoScalarInt(const_cast(*this)), - // std::forward(args)...); } else if (bitwidth <= 32) { return - // callable(detail::VariableInfoScalarInt(const_cast(*this)), - // std::forward(args)...); } else if (bitwidth <= 64) { return callable( detail::VariableInfoScalarInt(const_cast(*this)), @@ -761,35 +772,35 @@ auto VariableInfo::dispatchHelper(Callable &&callable, Args &&...args) const { } inline VariableInfo::VariableInfo(uint32_t bitwidth_, bool is_real_) { - platform::write_field(misc, bitwidth_, kBitwidthWidth, kBitwidthOffset); - platform::write_field(misc, is_real_, kIsRealWidth, kIsRealOffset); + platform::write_field(m_misc, bitwidth_, kBitwidthWidth, kBitwidthOffset); + platform::write_field(m_misc, is_real_, kIsRealWidth, kIsRealOffset); dispatchHelper([](auto obj) { obj.construct(); }); last_written_encode_type(EncodingType::BINARY); } inline uint32_t VariableInfo::emitValueChange(uint64_t current_time_index, const uint64_t val) { - const auto old_size = size(); + const uint64_t old_size = size(); dispatchHelper([=](auto obj) { obj.emitValueChange(current_time_index, val); }); last_written_encode_type(EncodingType::BINARY); - return size() - old_size; + return static_cast(size() - old_size); } inline uint32_t VariableInfo::emitValueChange( uint64_t current_time_index, const uint32_t *val, EncodingType encoding ) { - const auto old_size = size(); + const uint64_t old_size = size(); dispatchHelper([=](auto obj) { obj.emitValueChange(current_time_index, val, encoding); }); last_written_encode_type(encoding); - return size() - old_size; + return static_cast(size() - old_size); } inline uint32_t VariableInfo::emitValueChange( uint64_t current_time_index, const uint64_t *val, EncodingType encoding ) { - const auto old_size = size(); + const uint64_t old_size = size(); dispatchHelper([=](auto obj) { obj.emitValueChange(current_time_index, val, encoding); }); last_written_encode_type(encoding); - return size() - old_size; + return static_cast(size() - old_size); } inline void VariableInfo::dumpInitialBits(std::vector &buf) const { @@ -801,7 +812,7 @@ inline void VariableInfo::dumpValueChanges(std::vector &buf) const { } inline uint64_t VariableInfo::last_written_bytes() const { - const auto encoding = last_written_encode_type(); + const EncodingType encoding = last_written_encode_type(); return dispatchHelper([encoding](auto obj) { return obj.computeBytesNeeded(encoding); }); } diff --git a/include/fstcpp/fstcpp_writer.cpp b/include/fstcpp/fstcpp_writer.cpp index 6df611869..b965e80b7 100644 --- a/include/fstcpp/fstcpp_writer.cpp +++ b/include/fstcpp/fstcpp_writer.cpp @@ -9,9 +9,7 @@ // C++ standard library headers #include #include -#include #include -#include #include #include #include @@ -25,8 +23,6 @@ #include "fstcpp/fstcpp_stream_write_helper.h" #include "fstcpp/fstcpp_variable_info.h" -using namespace std; - // AT(x) is used to access vector at index x, and it will throw exception if out of bound // in debug mode, but in release mode, it will not throw exception // Usually you should only need AT(x) only at very hot code path. @@ -41,54 +37,54 @@ namespace fst { namespace detail { void BlackoutData::emitDumpActive(uint64_t current_timestamp, bool enable) { - StreamVectorWriteHelper h(buffer); - h.writeUIntBE(enable).writeLEB128(current_timestamp - previous_timestamp); - ++count; + StreamVectorWriteHelper h(m_buffer); + h.writeUIntBE(enable).writeLEB128(current_timestamp - m_previous_timestamp); + ++m_count; } ValueChangeData::ValueChangeData() { - variable_infos.reserve(1024); + m_variable_infos.reserve(1024); } ValueChangeData::~ValueChangeData() = default; void ValueChangeData::keepOnlyTheLatestValue() { - for (auto &v : variable_infos) { + for (VariableInfo &v : m_variable_infos) { v.keepOnlyTheLatestValue(); } - FST_CHECK(not timestamps.empty()); - timestamps.front() = timestamps.back(); - timestamps.resize(1); + FST_CHECK(!m_timestamps.empty()); + m_timestamps.front() = m_timestamps.back(); + m_timestamps.resize(1); } } // namespace detail void Writer::open(const string_view_pair name) { - FST_CHECK(not main_fst_file_.is_open()); - main_fst_file_.open(string(name.first, name.second), ios::binary); + FST_CHECK(!m_main_fst_file_.is_open()); + m_main_fst_file_.open(std::string(name.m_data, name.m_size), std::ios::binary); // reserve space for header, we will write it at Close(), append geometry and hierarchy at the // end wave data will be flushed in between - main_fst_file_.seekp(kSharedBlockHeaderSize + HeaderInfo::total_size, ios_base::beg); + m_main_fst_file_.seekp(kSharedBlockHeaderSize + HeaderInfo::total_size, std::ios_base::beg); } void Writer::close() { - if (not main_fst_file_.is_open()) return; + if (!m_main_fst_file_.is_open()) return; // Finalize header fields - if (header_.date[0] == '\0') { + if (m_header_.m_date[0] == '\0') { // date is not set yet, set to the current date setDate(); } - if (header_.start_time == kInvalidTime) { - header_.start_time = 0; + if (m_header_.m_start_time == kInvalidTime) { + m_header_.m_start_time = 0; } - flushValueChangeData_(value_change_data_, main_fst_file_); - appendGeometry_(main_fst_file_); - appendHierarchy_(main_fst_file_); - appendBlackout_(main_fst_file_); + flushValueChangeData_(m_value_change_data_, m_main_fst_file_); + appendGeometry_(m_main_fst_file_); + appendHierarchy_(m_main_fst_file_); + appendBlackout_(m_main_fst_file_); // Note: write header seek to 0, so we need to do // this after all append operations - writeHeader_(header_, main_fst_file_); - main_fst_file_.close(); + writeHeader_(m_header_, m_main_fst_file_); + m_main_fst_file_.close(); } ///////////////////////////////////////// @@ -99,20 +95,20 @@ void Writer::setScope( const string_view_pair scopename, const string_view_pair scopecomp ) { - FST_CHECK(not hierarchy_finalized_); - StreamVectorWriteHelper h(hierarchy_buffer_); + FST_CHECK(!m_hierarchy_finalized_); + StreamVectorWriteHelper h(m_hierarchy_buffer_); h // .writeU8Enum(Hierarchy::ScopeControlType::VCD_SCOPE) .writeU8Enum(scopetype) .writeString0(scopename) .writeString0(scopecomp); - ++header_.num_scopes; + ++m_header_.m_num_scopes; } void Writer::upscope() { - FST_CHECK(not hierarchy_finalized_); + FST_CHECK(!m_hierarchy_finalized_); // TODO: shall we inline it? - StreamVectorWriteHelper h(hierarchy_buffer_); + StreamVectorWriteHelper h(m_hierarchy_buffer_); h.writeU8Enum(Hierarchy::ScopeControlType::VCD_UPSCOPE); } @@ -123,13 +119,13 @@ Handle Writer::createVar( const string_view_pair name, Handle alias_handle ) { - FST_CHECK(not hierarchy_finalized_); + FST_CHECK(!m_hierarchy_finalized_); FST_CHECK_LE(bitwidth, VariableInfo::kMaxSupportedBitwidth); // write hierarchy entry: type, direction, name, length, alias - StreamVectorWriteHelper h(hierarchy_buffer_); + StreamVectorWriteHelper h(m_hierarchy_buffer_); // determine real/string handling like original C implementation - bool is_real = false; + bool is_real{false}; switch (vartype) { case Hierarchy::VarType::VCD_REAL: case Hierarchy::VarType::VCD_REAL_PARAMETER: @@ -144,17 +140,17 @@ Handle Writer::createVar( default: break; } - if (alias_handle > header_.num_handles) { + if (alias_handle > m_header_.m_num_handles) { // sanitize alias_handle = 0; } - const bool is_alias = alias_handle != 0; - // This counter is incremented whether alias or non-alias - ++header_.num_vars; - if (not is_alias) { + const bool is_alias{alias_handle != 0}; + // This counter is incremented whether alias || non-alias + ++m_header_.m_num_vars; + if (!is_alias) { // This counter is incremented only for non-alias variables - ++header_.num_handles; - alias_handle = header_.num_handles; + ++m_header_.m_num_handles; + alias_handle = static_cast(m_header_.m_num_handles); } h // @@ -165,40 +161,41 @@ Handle Writer::createVar( .writeLEB128(is_alias ? alias_handle : 0); // If alias_handle == 0, we must allocate geom/valpos/curval entries and create a new handle - if (not is_alias) { - StreamVectorWriteHelper g(geometry_buffer_); + if (!is_alias) { + StreamVectorWriteHelper g(m_geometry_buffer_); // I don't know why the original C implementation encode bitwidth again - const uint32_t geom_len = (bitwidth == 0 ? uint32_t(-1) : is_real ? uint32_t(0) : bitwidth); + const uint32_t geom_len{(bitwidth == 0 ? uint32_t(-1) : is_real ? uint32_t(0) : bitwidth)}; g.writeLEB128(geom_len); - value_change_data_.variable_infos.emplace_back(bitwidth, is_real); + m_value_change_data_.m_variable_infos.emplace_back(bitwidth, is_real); } return alias_handle; } +// TODO // LCOV_EXCL_START -Handle Writer::createVar2( - Hierarchy::VarType vartype, - Hierarchy::VarDirection vardir, - uint32_t bitwidth, - const string_view_pair name, - Handle alias_handle, - const string_view_pair type, - Hierarchy::SupplementalVarType svt, - Hierarchy::SupplementalDataType sdt -) { - FST_CHECK(not hierarchy_finalized_); - (void)vartype; - (void)vardir; - (void)bitwidth; - (void)name; - (void)alias_handle; - (void)type; - (void)svt; - (void)sdt; - throw runtime_error("TODO"); - return 0; -} +// Handle Writer::createVar2( +// Hierarchy::VarType vartype, +// Hierarchy::VarDirection vardir, +// uint32_t bitwidth, +// const string_view_pair name, +// Handle alias_handle, +// const string_view_pair type, +// Hierarchy::SupplementalVarType svt, +// Hierarchy::SupplementalDataType sdt +// ) { +// FST_CHECK(!m_hierarchy_finalized_); +// (void)vartype; +// (void)vardir; +// (void)bitwidth; +// (void)name; +// (void)alias_handle; +// (void)type; +// (void)svt; +// (void)sdt; +// throw std::runtime_error("TODO"); +// return 0; +// } // LCOV_EXCL_STOP ///////////////////////////////////////// @@ -207,44 +204,42 @@ Handle Writer::createVar2( void Writer::emitTimeChange(uint64_t tim) { finalizeHierarchy_(); - if (value_change_data_usage_ > value_change_data_flush_threshold_ or flush_pending_) { - flushValueChangeData_(value_change_data_, main_fst_file_); + if (m_value_change_data_usage_ > m_value_change_data_flush_threshold_ || m_flush_pending_) { + flushValueChangeData_(m_value_change_data_, m_main_fst_file_); } // Update header - header_.start_time = min(header_.start_time, tim); - header_.end_time = tim; + m_header_.m_start_time = std::min(m_header_.m_start_time, tim); + m_header_.m_end_time = tim; - if (value_change_data_.timestamps.empty() or value_change_data_.timestamps.back() != tim) { - value_change_data_.timestamps.push_back(tim); + if (m_value_change_data_.m_timestamps.empty() || + m_value_change_data_.m_timestamps.back() != tim) { + m_value_change_data_.m_timestamps.push_back(tim); } } -void Writer::emitDumpActive(bool enable) { - // TODO: this API is not fully understood, need to check - FST_CHECK(not value_change_data_.timestamps.empty()); - blackout_data_.emitDumpActive(value_change_data_.timestamps.back(), enable); -} - -template -uint64_t emitValueHelperStaticDispatch_( - VariableInfo *var_info, const uint64_t time_index, U &&...val -) { - return static_cast(var_info)->emitValueChange(time_index, std::forward(val)...); -} +// TODO +// void Writer::emitDumpActive(bool enable) { +// // TODO: this API is not fully understood, need to check +// FST_CHECK(!m_value_change_data_.m_timestamps.empty()); +// m_blackout_data_.emitDumpActive(m_value_change_data_.m_timestamps.back(), enable); +// } template void Writer::emitValueChangeHelper_(Handle handle, T &&...val) { // Let data prefetch go first - auto &var_info = value_change_data_.variable_infos AT(handle - 1); + VariableInfo &var_info = m_value_change_data_.m_variable_infos AT(handle - 1); +#if defined(__GNUC__) || defined(__clang__) __builtin_prefetch(var_info.data_ptr() + var_info.size() - 1, 1, 0); +#endif finalizeHierarchy_(); // Original implementation: virtual, but vtable is too costly, we switch to if-else static // dispatch - value_change_data_usage_ += - var_info.emitValueChange(value_change_data_.timestamps.size() - 1, std::forward(val)...); + m_value_change_data_usage_ += var_info.emitValueChange( + m_value_change_data_.m_timestamps.size() - 1, std::forward(val)... + ); } void Writer::emitValueChange(Handle handle, const uint32_t *val, EncodingType encoding) { @@ -261,7 +256,7 @@ void Writer::emitValueChange(Handle handle, uint64_t val) { void Writer::emitValueChange(Handle handle, const char *val) { finalizeHierarchy_(); - auto &var_info = value_change_data_.variable_infos AT(handle - 1); + VariableInfo &var_info = m_value_change_data_.m_variable_infos AT(handle - 1); // For double handles, const char* is interpreted as a double* (8B) // This double shall be written out as raw IEEE 754 double @@ -272,65 +267,67 @@ void Writer::emitValueChange(Handle handle, const char *val) { } // For normal integer handles, const char* is "01xz..." (1B per bit) - const uint32_t bitwidth = var_info.bitwidth(); + const uint32_t bitwidth{var_info.bitwidth()}; FST_DCHECK_NE(bitwidth, 0); val += bitwidth; - thread_local static vector packed_value_buffer; - const unsigned num_words = (bitwidth + 63) / 64; - packed_value_buffer.assign(num_words, 0); + thread_local static std::vector t_packed_value_buffer; + const unsigned num_words{(bitwidth + 63) / 64}; + t_packed_value_buffer.assign(num_words, 0); for (unsigned i = 0; i < num_words; ++i) { - const char *start = val - std::min((i + 1) * 64, bitwidth); - const char *end = val - 64 * i; - packed_value_buffer[i] = 0; + const char *start{val - std::min((i + 1) * 64, bitwidth)}; + const char *end{val - 64 * i}; + t_packed_value_buffer[i] = 0; for (const char *p = start; p < end; ++p) { // No checking for invalid characters, follow original C implementation - packed_value_buffer[i] <<= 1; - packed_value_buffer[i] |= (*p - '0'); + t_packed_value_buffer[i] <<= 1; + t_packed_value_buffer[i] |= static_cast(*p - '0'); } } if (bitwidth <= 64) { - emitValueChange(handle, packed_value_buffer.front()); + emitValueChange(handle, t_packed_value_buffer.front()); } else { - emitValueChange(handle, packed_value_buffer.data(), EncodingType::BINARY); + emitValueChange(handle, t_packed_value_buffer.data(), EncodingType::BINARY); } } ///////////////////////////////////////// // File flushing functions ///////////////////////////////////////// -void Writer::writeHeader_(const Header &header, ostream &os) { +void Writer::writeHeader_(const Header &header, std::ostream &os) { StreamWriteHelper h(os); - static char kDefaultWriterName[sizeof(header.writer)] = "fstcppWriter"; - const char *writer_name = header.writer[0] == '\0' ? kDefaultWriterName : header.writer; + static char kDefaultWriterName[sizeof(header.m_writer)] = "fstcppWriter"; + const char *writer_name = header.m_writer[0] == '\0' ? kDefaultWriterName : header.m_writer; // Actual write h // - .seek(streamoff(0), ios_base::beg) + .seek(std::streamoff(0), std::ios_base::beg) .writeBlockHeader(BlockType::HEADER, HeaderInfo::total_size) - .writeUInt(header.start_time) - .writeUInt(header.end_time) + .writeUInt(header.m_start_time) + .writeUInt(header.m_end_time) .writeFloat(HeaderInfo::kEndianessMagicIdentifier) - .writeUInt(header.writer_memory_use) - .writeUInt(header.num_scopes) - .writeUInt(header.num_vars) - .writeUInt(header.num_handles) - .writeUInt(header.num_value_change_data_blocks) - .writeUInt(header.timescale) - .write(writer_name, sizeof(header.writer)) - .write(header.date, sizeof(header.date)) + .writeUInt(header.m_writer_memory_use) + .writeUInt(header.m_num_scopes) + .writeUInt(header.m_num_vars) + .writeUInt(header.m_num_handles) + .writeUInt(header.m_num_value_change_data_blocks) + .writeUInt(header.m_timescale) + .write(writer_name, sizeof(header.m_writer)) + .write(header.m_date, sizeof(header.m_date)) .fill('\0', HeaderInfo::Size::reserved) - .writeUInt(static_cast(header.filetype)) - .writeUInt(header.timezero); + .writeUInt(static_cast(header.m_filetype)) + .writeUInt(header.m_timezero); FST_DCHECK_EQ(os.tellp(), HeaderInfo::total_size + kSharedBlockHeaderSize); -}; +} namespace { // compression helpers // These API pass compressed_data to avoid frequent reallocations -void compressUsingLz4(const vector &uncompressed_data, vector &compressed_data) { +void compressUsingLz4( + const std::vector &uncompressed_data, std::vector &compressed_data +) { const int uncompressed_size = uncompressed_data.size(); const int compressed_bound = LZ4_compressBound(uncompressed_size); compressed_data.resize(compressed_bound); @@ -344,7 +341,7 @@ void compressUsingLz4(const vector &uncompressed_data, vector } void compressUsingZlib( - const vector &uncompressed_data, vector &compressed_data, int level + const std::vector &uncompressed_data, std::vector &compressed_data, int level ) { // compress using zlib const uLong uncompressed_size = uncompressed_data.size(); @@ -358,17 +355,17 @@ void compressUsingZlib( level ); if (z_status != Z_OK) { - throw runtime_error( - "Failed to compress data with zlib, error code: " + to_string(z_status) + throw std::runtime_error( + "Failed to compress data with zlib, error code: " + std::to_string(z_status) ); } compressed_data.resize(compressed_bound); } -pair selectSmaller( - const vector &compressed_data, const vector &uncompressed_data +std::pair selectSmaller( + const std::vector &compressed_data, const std::vector &uncompressed_data ) { - pair ret; + std::pair ret; if (compressed_data.size() < uncompressed_data.size()) { ret.first = compressed_data.data(); ret.second = compressed_data.size(); @@ -383,103 +380,109 @@ pair selectSmaller( // AppendHierarchy_ and AppendGeometry_ shares a very similar structure // But they are slightly different in the original C implementation... -void Writer::appendGeometry_(ostream &os) { - if (geometry_buffer_.empty()) { +void Writer::appendGeometry_(std::ostream &os) { + if (m_geometry_buffer_.empty()) { // skip the geometry block if there is no data return; } - vector geometry_buffer_compressed_; - compressUsingZlib(geometry_buffer_, geometry_buffer_compressed_, 9); + std::vector geometry_buffer_compressed_{}; + compressUsingZlib(m_geometry_buffer_, geometry_buffer_compressed_, 9); // TODO: Replace with structured binding in C++17 - const auto selected_pair = selectSmaller(geometry_buffer_compressed_, geometry_buffer_); - const auto selected_data = selected_pair.first; - const auto selected_size = selected_pair.second; + const std::pair selected_pair = + selectSmaller(geometry_buffer_compressed_, m_geometry_buffer_); + const uint8_t *selected_data = selected_pair.first; + const size_t selected_size = selected_pair.second; StreamWriteHelper h(os); h // - .seek(0, ios_base::end) + .seek(0, std::ios_base::end) // 16 is for the uncompressed_size and header_.num_handles .writeBlockHeader(BlockType::GEOMETRY, selected_size + 16) - .writeUInt(geometry_buffer_.size()) + .writeUInt(m_geometry_buffer_.size()) // I don't know why the original C implementation write num_handles again here // but we have to follow it - .writeUInt(header_.num_handles) + .writeUInt(m_header_.m_num_handles) .write(selected_data, selected_size); } -void Writer::appendHierarchy_(ostream &os) { - if (hierarchy_buffer_.empty()) { +void Writer::appendHierarchy_(std::ostream &os) { + if (m_hierarchy_buffer_.empty()) { // skip the hierarchy block if there is no data return; } // compress hierarchy_buffer_ using LZ4. - const int compressed_bound = LZ4_compressBound(hierarchy_buffer_.size()); - vector hierarchy_buffer_compressed_(compressed_bound); - const int compressed_size = LZ4_compress_default( - reinterpret_cast(hierarchy_buffer_.data()), + const int compressed_bound{LZ4_compressBound(m_hierarchy_buffer_.size())}; + std::vector hierarchy_buffer_compressed_(compressed_bound); + const int compressed_size{LZ4_compress_default( + reinterpret_cast(m_hierarchy_buffer_.data()), reinterpret_cast(hierarchy_buffer_compressed_.data()), - hierarchy_buffer_.size(), + m_hierarchy_buffer_.size(), compressed_bound - ); + )}; StreamWriteHelper h(os); h // - .seek(0, ios_base::end) + .seek(0, std::ios_base::end) // +16 is for the uncompressed_size .writeBlockHeader(BlockType::HIERARCHY_LZ4_COMPRESSED, compressed_size + 8) - .writeUInt(hierarchy_buffer_.size()) + .writeUInt(m_hierarchy_buffer_.size()) .write(hierarchy_buffer_compressed_.data(), compressed_size); } -void Writer::appendBlackout_(ostream &os) { - if (blackout_data_.count == 0) { +void Writer::appendBlackout_(std::ostream &os) { + if (m_blackout_data_.m_count == 0) { // skip the blackout block if there is no data return; } - const vector &blackout_data = blackout_data_.buffer; - const auto begin_of_blackout_block = os.tellp(); + const std::vector &blackout_data = m_blackout_data_.m_buffer; + const std::streampos begin_of_blackout_block = os.tellp(); StreamWriteHelper h(os); h // // skip the block header - .seek(kSharedBlockHeaderSize, ios_base::cur) + .seek(kSharedBlockHeaderSize, std::ios_base::cur) // Note: we cannot know the size beforehand since this length is LEB128 encoded .writeLEB128(blackout_data.size()) .write(blackout_data.data(), blackout_data.size()); - const auto size_of_blackout_block = os.tellp() - begin_of_blackout_block; + const std::streamoff size_of_blackout_block = os.tellp() - begin_of_blackout_block; h // // go back to the beginning of the block - .seek(begin_of_blackout_block, ios_base::beg) + .seek(begin_of_blackout_block, std::ios_base::beg) // and write the block header - .writeBlockHeader(BlockType::BLACKOUT, size_of_blackout_block - kSharedBlockHeaderSize); + .writeBlockHeader( + BlockType::BLACKOUT, + static_cast(size_of_blackout_block - kSharedBlockHeaderSize) + ); } -void detail::ValueChangeData::writeInitialBits(vector &os) const { +void detail::ValueChangeData::writeInitialBits(std::vector &os) const { // Build vc_bits_data by concatenating each variable's initial bits as documented. // We will not compress for now; just generate the raw bytes and print summary to stdout. - for (size_t i = 0; i < variable_infos.size(); ++i) { - auto &vref = variable_infos[i]; + for (size_t i{0}; i < m_variable_infos.size(); ++i) { + const VariableInfo &vref = m_variable_infos[i]; vref.dumpInitialBits(os); } } -vector> detail::ValueChangeData::computeWaveData() const { - const size_t N = variable_infos.size(); - vector> data(N); - for (size_t i = 0; i < N; ++i) { - variable_infos[i].dumpValueChanges(data[i]); +std::vector> detail::ValueChangeData::computeWaveData() const { + const size_t N{m_variable_infos.size()}; + std::vector> data(N); + for (size_t i{0}; i < N; ++i) { + m_variable_infos[i].dumpValueChanges(data[i]); } return data; } -vector detail::ValueChangeData::uniquifyWaveData(vector> &data) { +std::vector detail::ValueChangeData::uniquifyWaveData( + std::vector> &data +) { // After this function, positions[i] is: // - = 0: If data[i] is unique (first occurrence) // - < 0: If data[i] is a duplicate, encoded as -(original_index + 1) - vector positions(data.size(), 0); + std::vector positions(data.size(), 0); struct MyHash { - size_t operator()(const vector *vec) const { + size_t operator()(const std::vector *vec) const { size_t seed = 0; for (auto v : *vec) { seed ^= v + 0x9e3779b9 + (seed << 6) + (seed >> 2); @@ -488,11 +491,11 @@ vector detail::ValueChangeData::uniquifyWaveData(vector } }; struct MyEqual { - bool operator()(const vector *a, const vector *b) const { + bool operator()(const std::vector *a, const std::vector *b) const { return *a == *b; } }; - unordered_map *, int64_t, MyHash, MyEqual> data_map; + std::unordered_map *, int64_t, MyHash, MyEqual> data_map; for (size_t i = 0; i < data.size(); ++i) { if (data[i].empty()) { continue; @@ -500,9 +503,9 @@ vector detail::ValueChangeData::uniquifyWaveData(vector // insert vec->i to data_map if not exists auto p = data_map.emplace(&data[i], static_cast(i)); auto it = p.first; - auto inserted = p.second; + const bool inserted{p.second}; - if (not inserted) { + if (!inserted) { // duplicated wave data found positions[i] = -(it->second + 1); // clear data to save memory @@ -513,9 +516,9 @@ vector detail::ValueChangeData::uniquifyWaveData(vector } uint64_t detail::ValueChangeData::encodePositionsAndwriteUniqueWaveData( - ostream &os, - const vector> &data, - vector &positions, + std::ostream &os, + const std::vector> &data, + std::vector &positions, WriterPackType pack_type ) { // After this function, positions[i] is: @@ -527,7 +530,7 @@ uint64_t detail::ValueChangeData::encodePositionsAndwriteUniqueWaveData( StreamWriteHelper h(os); int64_t previous_size = 1; uint64_t written_count = 0; - vector compressed_data; + std::vector compressed_data; for (size_t i = 0; i < positions.size(); ++i) { if (positions[i] < 0) { // duplicate (negative index), do nothing @@ -537,12 +540,13 @@ uint64_t detail::ValueChangeData::encodePositionsAndwriteUniqueWaveData( // try to compress const uint8_t *selected_data; size_t selected_size; - if (pack_type == WriterPackType::NO_COMPRESSION or data[i].size() <= 32) { + if (pack_type == WriterPackType::NO_COMPRESSION || data[i].size() <= 32) { selected_data = data[i].data(); selected_size = data[i].size(); } else { compressUsingLz4(data[i], compressed_data); - const auto selected_pair = selectSmaller(compressed_data, data[i]); + const std::pair selected_pair = + selectSmaller(compressed_data, data[i]); selected_data = selected_pair.first; selected_size = selected_pair.second; } @@ -550,7 +554,7 @@ uint64_t detail::ValueChangeData::encodePositionsAndwriteUniqueWaveData( // non-empty unique data, write it written_count++; - streamoff bytes_written; + std::streamoff bytes_written; h // .beginOffset(bytes_written) // FST spec: 0 means no compression, >0 for the size of the original data @@ -565,7 +569,7 @@ uint64_t detail::ValueChangeData::encodePositionsAndwriteUniqueWaveData( } void detail::ValueChangeData::writeEncodedPositions( - const vector &encoded_positions, ostream &os + const std::vector &encoded_positions, std::ostream &os ) { // Encode positions with the specified run/varint rules into a varint buffer. StreamWriteHelper h(os); @@ -613,20 +617,20 @@ void detail::ValueChangeData::writeEncodedPositions( } } -void detail::ValueChangeData::writeTimestamps(vector &os) const { +void detail::ValueChangeData::writeTimestamps(std::vector &os) const { // Build LEB128-encoded delta stream (first delta is timestamp[0] - 0) StreamVectorWriteHelper h(os); - uint64_t prev = 0; - for (size_t i = 0; i < timestamps.size(); ++i) { - const uint64_t cur = timestamps[i]; - const uint64_t delta = cur - prev; + uint64_t prev{0}; + for (size_t i{0}; i < m_timestamps.size(); ++i) { + const uint64_t cur{m_timestamps[i]}; + const uint64_t delta{cur - prev}; h.writeLEB128(delta); prev = cur; } } void Writer::flushValueChangeDataConstPart_( - const detail::ValueChangeData &vcd, ostream &os, WriterPackType pack_type + const detail::ValueChangeData &vcd, std::ostream &os, WriterPackType pack_type ) { // 0. setup StreamWriteHelper h(os); @@ -635,80 +639,83 @@ void Writer::flushValueChangeDataConstPart_( // FST_BL_VCDATA_DYN_ALIAS2 (8) maps to WaveDataVersion3 in fst_file.h // The positions we cannot fill in yet const auto p_tmp1 = [&]() { - streamoff start_pos, memory_usage_pos; + std::streamoff start_pos, memory_usage_pos; h // .beginOffset(start_pos) // record start position .writeBlockHeader(BlockType::WAVE_DATA_VERSION3, 0 /* Length placeholder 0 */) - .writeUInt(vcd.timestamps.front()) - .writeUInt(vcd.timestamps.back()) + .writeUInt(vcd.m_timestamps.front()) + .writeUInt(vcd.m_timestamps.back()) .beginOffset(memory_usage_pos) // record memory usage position .writeUInt(0); // placeholder for memory usage - return make_pair(start_pos, memory_usage_pos); + return std::make_pair(start_pos, memory_usage_pos); }(); - const auto start_pos = p_tmp1.first; - const auto memory_usage_pos = p_tmp1.second; + const std::streamoff start_pos{p_tmp1.first}; + const std::streamoff memory_usage_pos{p_tmp1.second}; // 2. Bits Section { - vector bits_data; + std::vector bits_data; vcd.writeInitialBits(bits_data); - vector bits_data_compressed; + std::vector bits_data_compressed; const uint8_t *selected_data; size_t selected_size; - if (pack_type == WriterPackType::NO_COMPRESSION or bits_data.size() < 32) { + if (pack_type == WriterPackType::NO_COMPRESSION || bits_data.size() < 32) { selected_data = bits_data.data(); selected_size = bits_data.size(); } else { compressUsingZlib(bits_data, bits_data_compressed, 4); - const auto selected_pair = selectSmaller(bits_data_compressed, bits_data); + const std::pair selected_pair = + selectSmaller(bits_data_compressed, bits_data); selected_data = selected_pair.first; selected_size = selected_pair.second; } - h // - .writeLEB128(bits_data.size()) // uncompressed length - .writeLEB128(selected_size) // compressed length - .writeLEB128(vcd.variable_infos.size()) // bits count + h // + .writeLEB128(bits_data.size()) // uncompressed length + .writeLEB128(selected_size) // compressed length + .writeLEB128(vcd.m_variable_infos.size()) // bits count .write(selected_data, selected_size); } // 3. Waves Section // Note: We need positions for the next section const auto p_tmp2 = [&, pack_type]() { - auto wave_data = vcd.computeWaveData(); - const size_t memory_usage = - accumulate(wave_data.begin(), wave_data.end(), size_t(0), [](size_t a, const auto &b) { - return a + b.size(); - }); - auto positions = vcd.uniquifyWaveData(wave_data); + std::vector> wave_data{vcd.computeWaveData()}; + const size_t memory_usage{std::accumulate( + wave_data.begin(), + wave_data.end(), + size_t(0), + [](size_t a, const std::vector &b) { return a + b.size(); } + )}; + std::vector positions{vcd.uniquifyWaveData(wave_data)}; h // Note: this is not a typo, I expect we shall write count here. // but the spec indeed write vcd.variable_infos.size(), // which is repeated 1 times in header block, 2 times in valuechange block - .writeLEB128(vcd.variable_infos.size()) + .writeLEB128(vcd.m_variable_infos.size()) .writeUInt(uint8_t('4')); - const uint64_t count = detail::ValueChangeData::encodePositionsAndwriteUniqueWaveData( + const uint64_t count{detail::ValueChangeData::encodePositionsAndwriteUniqueWaveData( os, wave_data, positions, pack_type - ); + )}; (void)count; - return make_pair(positions, memory_usage); + return std::make_pair(positions, memory_usage); }(); - const auto positions = p_tmp2.first; - const auto memory_usage = p_tmp2.second; + const std::vector positions{p_tmp2.first}; + const size_t memory_usage{p_tmp2.second}; // 4. Position Section { - const auto pos_begin = os.tellp(); + const std::streampos pos_begin{os.tellp()}; vcd.writeEncodedPositions(positions, os); - const uint64_t pos_size = os.tellp() - pos_begin; + const uint64_t pos_size{static_cast(os.tellp() - pos_begin)}; h.writeUInt(pos_size); // Length comes AFTER data for positions } // 5. Time Section { - vector time_data; + std::vector time_data; vcd.writeTimestamps(time_data); - vector time_data_compressed; + std::vector time_data_compressed; const uint8_t *selected_data; size_t selected_size; if (pack_type == WriterPackType::NO_COMPRESSION) { @@ -716,36 +723,37 @@ void Writer::flushValueChangeDataConstPart_( selected_size = time_data.size(); } else { compressUsingZlib(time_data, time_data_compressed, 9); - const auto selected_pair = selectSmaller(time_data_compressed, time_data); + const std::pair selected_pair = + selectSmaller(time_data_compressed, time_data); selected_data = selected_pair.first; selected_size = selected_pair.second; } - h // - .write(selected_data, selected_size) // time data - .writeUInt(time_data.size()) // uncompressed len - .writeUInt(selected_size) // compressed len - .writeUInt(uint64_t(vcd.timestamps.size())); // count + h // + .write(selected_data, selected_size) // time data + .writeUInt(time_data.size()) // uncompressed len + .writeUInt(selected_size) // compressed len + .writeUInt(uint64_t(vcd.m_timestamps.size())); // count } // 6. Patch Block Length and Memory Required - streamoff end_pos; + std::streamoff end_pos{0}; h // .beginOffset(end_pos) // Patch Block Length (after 1 byte Type) - .seek(start_pos + streamoff(1), ios_base::beg) - .writeUInt(end_pos - start_pos - 1) + .seek(start_pos + std::streamoff(1), std::ios_base::beg) + .writeUInt(static_cast(end_pos - start_pos - 1)) // Patch Memory Required - .seek(memory_usage_pos, ios_base::beg) - .writeUInt(memory_usage) + .seek(memory_usage_pos, std::ios_base::beg) + .writeUInt(static_cast(memory_usage)) // Restore position to end - .seek(end_pos, ios_base::beg); + .seek(end_pos, std::ios_base::beg); } namespace { // Helper functions for createEnumTable -void appendEscToString(const string_view_pair in, string &out) { - for (size_t i = 0; i < in.second; ++i) { - const char c = in.first[i]; +void appendEscToString(const string_view_pair in, std::string &out) { + for (size_t i{0}; i < in.m_size; ++i) { + const char c{in.m_data[i]}; switch (c) { // clang-format off case '\a': { out += "\\a"; break; } @@ -786,9 +794,9 @@ void Writer::setAttrBegin( const string_view_pair attrname, uint64_t arg ) { - FST_CHECK(not hierarchy_finalized_); + FST_CHECK(!m_hierarchy_finalized_); - StreamVectorWriteHelper h(hierarchy_buffer_); + StreamVectorWriteHelper h(m_hierarchy_buffer_); if (attrtype > Hierarchy::AttrType::MAX) { attrtype = Hierarchy::AttrType::MISC; @@ -835,48 +843,42 @@ void Writer::setAttrBegin( .writeLEB128(arg); } -namespace { - -// overload for string += string_view_ -// Remove this once C++17 is required -} // namespace - EnumHandle Writer::createEnumTable( const string_view_pair name, uint32_t min_valbits, - const vector> &literal_val_arr + const std::vector> &literal_val_arr ) { - EnumHandle handle = 0; + EnumHandle handle{0}; - if (name.second == 0 or literal_val_arr.empty()) { + if (name.m_size == 0 || literal_val_arr.empty()) { return handle; } - string attr_str; + std::string attr_str; attr_str.reserve(256); - attr_str.append(name.first, name.second); + attr_str.append(name.m_data, name.m_size); attr_str += ' '; - attr_str += to_string(literal_val_arr.size()); + attr_str += std::to_string(literal_val_arr.size()); attr_str += ' '; for (const auto &p : literal_val_arr) { - const auto &literal = p.first; + const string_view_pair literal{p.first}; // literal appendEscToString(literal, attr_str); attr_str += ' '; } for (const auto &p : literal_val_arr) { - const auto &val = p.second; + const string_view_pair val{p.second}; // val (with padding) - if (min_valbits > 0 and val.second < min_valbits) { - attr_str.insert(attr_str.end(), min_valbits - val.second, '0'); + if (min_valbits > 0 && val.m_size < min_valbits) { + attr_str.insert(attr_str.end(), min_valbits - val.m_size, '0'); } appendEscToString(val, attr_str); attr_str += ' '; } attr_str.pop_back(); // remove last space - handle = ++enum_count_; + handle = ++m_enum_count_; setAttrBegin( Hierarchy::AttrType::MISC, Hierarchy::AttrSubType::MISC_ENUMTABLE, diff --git a/include/fstcpp/fstcpp_writer.h b/include/fstcpp/fstcpp_writer.h index 8ec161e67..e50a74214 100644 --- a/include/fstcpp/fstcpp_writer.h +++ b/include/fstcpp/fstcpp_writer.h @@ -12,7 +12,6 @@ #include #include #include -#include #include #if __cplusplus >= 201703L # include @@ -28,19 +27,20 @@ class Writer; namespace detail { -// We define WriterWaveData here for better code inlining, no forward declaration +// We define BlackoutData here for better code inlining, no forward declaration +// Blackout is not implemented yet struct BlackoutData { - std::vector buffer; - uint64_t previous_timestamp = 0; - uint64_t count = 0; + std::vector m_buffer{}; + uint64_t m_previous_timestamp{0}; + uint64_t m_count{0}; void emitDumpActive(uint64_t current_timestamp, bool enable); }; // We define ValueChangeData here for better code inlining, no forward declaration struct ValueChangeData { - std::vector variable_infos; - std::vector timestamps; + std::vector m_variable_infos{}; + std::vector m_timestamps{}; ValueChangeData(); ~ValueChangeData(); @@ -66,10 +66,31 @@ struct ValueChangeData { class Writer { friend class WriterTest; +private: + // File/memory buffers + // 1. For hierarchy and geometry, we do not keep the data structure, instead we just + // serialize them into buffers, and compress+write them at the end of file. + // 2. For header, we keep the data structure in memory since it is quite small + // 3. For wave data, we keep a complicated data structure in memory, + // and flush them to file when necessary + // 4. For blackout data, it is not implemented yet + std::ofstream m_main_fst_file_{}; + std::vector m_hierarchy_buffer_{}; + std::vector m_geometry_buffer_{}; + Header m_header_{}; + detail::BlackoutData m_blackout_data_{}; // Not implemented yet + detail::ValueChangeData m_value_change_data_{}; + bool m_hierarchy_finalized_{false}; + WriterPackType m_pack_type_{WriterPackType::LZ4}; + uint64_t m_value_change_data_usage_{0}; // Note: this value is just an estimation + uint64_t m_value_change_data_flush_threshold_{128 << 20}; // 128MB + uint32_t m_enum_count_{0}; + bool m_flush_pending_{false}; + public: Writer() {} Writer(const string_view_pair name) { - if (name.second != 0) open(name); + if (name.m_size != 0) open(name); } ~Writer() { close(); } @@ -85,22 +106,28 @@ public: ////////////////////////////// // Header manipulation API ////////////////////////////// - const Header &getHeader() const; - void setTimecale(int8_t timescale) { header_.timescale = timescale; } - void setWriter(const string_view_pair Writer) { - const auto len = std::min(Writer.second, sizeof(header_.writer)); - std::copy_n(Writer.first, len, header_.writer); - if (len != sizeof(header_.writer)) { - header_.writer[len] = '\0'; + const Header &getHeader() const { return m_header_; } + void setTimecale(int8_t timescale) { m_header_.m_timescale = timescale; } + void setWriter(const string_view_pair writer) { + const size_t len = std::min(writer.m_size, sizeof(m_header_.m_writer)); + std::copy_n(writer.m_data, len, m_header_.m_writer); + if (len != sizeof(m_header_.m_writer)) { + m_header_.m_writer[len] = '\0'; } } void setDate(const string_view_pair date_str) { - const auto len = date_str.second; - FST_CHECK_EQ(len, sizeof(header_.date) - 1); - std::copy_n(date_str.first, len, header_.date); - header_.date[len] = '\0'; + const size_t len = date_str.m_size; + FST_CHECK_EQ(len, sizeof(m_header_.m_date) - 1); + std::copy_n(date_str.m_data, len, m_header_.m_date); + m_header_.m_date[len] = '\0'; } - void setTimezero(int64_t timezero) { header_.timezero = timezero; } + void setDate(const std::tm *d) { setDate(make_string_view_pair(std::asctime(d))); } + void setDate() { + // set date to now + std::time_t t{std::time(nullptr)}; + setDate(std::localtime(&t)); + } + void setTimezero(int64_t timezero) { m_header_.m_timezero = timezero; } ////////////////////////////// // Change scope API @@ -121,8 +148,8 @@ public: const string_view_pair attrname, uint64_t arg ); - inline void setAttrEnd() { - hierarchy_buffer_.push_back( + void setAttrEnd() { + m_hierarchy_buffer_.push_back( static_cast(Hierarchy::ScopeControlType::GEN_ATTR_END) ); } @@ -131,7 +158,20 @@ public: uint32_t min_valbits, const std::vector> &literal_val_arr ); - inline void emitEnumTableRef(EnumHandle handle) { + template + EnumHandle createEnumTable( + const char *name, + uint32_t min_valbits, + const std::vector> &literal_val_arr + ) { + std::vector> arr{}; + arr.reserve(literal_val_arr.size()); + for (const auto &p : literal_val_arr) { + arr.emplace_back(make_string_view_pair(p.first), make_string_view_pair(p.second)); + } + return createEnumTable(make_string_view_pair(name), min_valbits, arr); + } + void emitEnumTableRef(EnumHandle handle) { setAttrBegin( Hierarchy::AttrType::MISC, Hierarchy::AttrSubType::MISC_ENUMTABLE, @@ -139,9 +179,9 @@ public: handle ); } - inline void setWriterPackType(WriterPackType pack_type) { - FST_CHECK(pack_type != WriterPackType::ZLIB and pack_type != WriterPackType::FASTLZ); - pack_type_ = pack_type; + void setWriterPackType(WriterPackType pack_type) { + FST_CHECK(pack_type != WriterPackType::ZLIB && pack_type != WriterPackType::FASTLZ); + m_pack_type_ = pack_type; } ////////////////////////////// @@ -154,22 +194,24 @@ public: const string_view_pair name, uint32_t alias_handle ); - Handle createVar2( - Hierarchy::VarType vartype, - Hierarchy::VarDirection vardir, - uint32_t bitwidth, - const string_view_pair name, - uint32_t alias_handle, - const string_view_pair type, - Hierarchy::SupplementalVarType svt, - Hierarchy::SupplementalDataType sdt - ); + // TODO + // Handle createVar2( + // Hierarchy::VarType vartype, + // Hierarchy::VarDirection vardir, + // uint32_t bitwidth, + // const string_view_pair name, + // uint32_t alias_handle, + // const string_view_pair type, + // Hierarchy::SupplementalVarType svt, + // Hierarchy::SupplementalDataType sdt + // ); ////////////////////////////// // Waveform API ////////////////////////////// void emitTimeChange(uint64_t tim); - void emitDumpActive(bool enable); + // TODO + // void emitDumpActive(bool enable); void emitValueChange( Handle handle, const uint32_t *val, EncodingType encoding = EncodingType::BINARY ); @@ -186,257 +228,36 @@ public: // We only ensure that this function works where Verilator use it. void emitValueChange(Handle handle, const char *val); - ////////////////////////////// - // Alias version - ////////////////////////////// - // Constructor - Writer(const char *name) : Writer(make_string_view_pair(name)) {} - Writer(const std::string &name) : Writer(make_string_view_pair(name.c_str(), name.size())) {} - // Open - inline void open(const char *name) { open(make_string_view_pair(name)); } - inline void open(const std::string &name) { - open(make_string_view_pair(name.c_str(), name.size())); - } - // setWriter - inline void setWriter(const char *Writer) { - if (Writer) setWriter(make_string_view_pair(Writer)); - } - inline void setWriter(const std::string &Writer) { - setWriter(make_string_view_pair(Writer.c_str(), Writer.size())); - } - // setDate - inline void setDate(const char *date_str) { - if (date_str) setDate(make_string_view_pair(date_str)); - } - inline void setDate(const std::string &date_str) { - setDate(make_string_view_pair(date_str.c_str(), date_str.size())); - } - inline void setDate(const std::tm *d) { setDate(make_string_view_pair(std::asctime(d))); } - inline void setDate() { - // set date to now - std::time_t t = std::time(nullptr); - setDate(std::localtime(&t)); - } - // CreateVar(2) - inline Handle createVar( - Hierarchy::VarType vartype, - Hierarchy::VarDirection vardir, - uint32_t bitwidth, - const char *name, - uint32_t alias_handle - ) { - FST_CHECK_NE(name, static_cast(nullptr)); - return createVar(vartype, vardir, bitwidth, make_string_view_pair(name), alias_handle); - } - inline Handle createVar( - Hierarchy::VarType vartype, - Hierarchy::VarDirection vardir, - uint32_t bitwidth, - const std::string &name, - uint32_t alias_handle - ) { - return createVar( - vartype, - vardir, - bitwidth, - make_string_view_pair(name.c_str(), name.size()), - alias_handle - ); - } - // setScope - inline void setScope( - Hierarchy::ScopeType scopetype, const std::string &scopename, const std::string &scopecomp - ) { - setScope( - scopetype, - make_string_view_pair(scopename.c_str(), scopename.size()), - make_string_view_pair(scopecomp.c_str(), scopecomp.size()) - ); - } - inline void setScope( - Hierarchy::ScopeType scopetype, const char *scopename, const char *scopecomp - ) { - setScope(scopetype, make_string_view_pair(scopename), make_string_view_pair(scopecomp)); - } - // setAttrBegin - inline void setAttrBegin( - Hierarchy::AttrType attrtype, - Hierarchy::AttrSubType subtype, - const char *attrname, - uint64_t arg - ) { - setAttrBegin(attrtype, subtype, make_string_view_pair(attrname), arg); - } - // CreateEnumTable - EnumHandle createEnumTable( - const char *name, - uint32_t min_valbits, - const std::vector> &literal_val_arr - ) { - std::vector> arr; - arr.reserve(literal_val_arr.size()); - for (const auto &p : literal_val_arr) { - arr.emplace_back(make_string_view_pair(p.first), make_string_view_pair(p.second)); - } - return createEnumTable(make_string_view_pair(name), min_valbits, arr); - } - // CreateVar2 - inline Handle createVar2( - Hierarchy::VarType vartype, - Hierarchy::VarDirection vardir, - uint32_t bitwidth, - const char *name, - uint32_t alias_handle, - const char *type, - Hierarchy::SupplementalVarType svt, - Hierarchy::SupplementalDataType sdt - ) { - return createVar2( - vartype, - vardir, - bitwidth, - make_string_view_pair(name), - alias_handle, - make_string_view_pair(type), - svt, - sdt - ); - } // Flush value change data - inline void flushValueChangeData() { flush_pending_ = true; } + void flushValueChangeData() { m_flush_pending_ = true; } -#if __cplusplus >= 201703L - // All APIs with string_view_pair --> define a - // string_view version and forward to the string_view_pair version - inline Writer(std::string_view name) - : Writer(make_string_view_pair(name.data(), name.size())) {} - inline void open(std::string_view name) { - open(make_string_view_pair(name.data(), name.size())); - } - inline void setWriter(std::string_view Writer) { - setWriter(make_string_view_pair(Writer.data(), Writer.size())); - } - inline void setDate(std::string_view date_str) { - setDate(make_string_view_pair(date_str.data(), date_str.size())); - } - - inline void setScope( - Hierarchy::ScopeType scopetype, std::string_view scopename, std::string_view scopecomp - ) { - setScope( - scopetype, - make_string_view_pair(scopename.data(), scopename.size()), - make_string_view_pair(scopecomp.data(), scopecomp.size()) - ); - } - - inline void setAttrBegin( - Hierarchy::AttrType attrtype, - Hierarchy::AttrSubType subtype, - std::string_view attrname, - uint64_t arg - ) { - setAttrBegin( - attrtype, subtype, make_string_view_pair(attrname.data(), attrname.size()), arg - ); - } - - EnumHandle createEnumTable( - std::string_view name, - uint32_t min_valbits, - const std::vector> &literal_val_arr - ) { - std::vector> arr; - arr.reserve(literal_val_arr.size()); - for (const auto &p : literal_val_arr) { - arr.emplace_back( - make_string_view_pair(p.first.data(), p.first.size()), - make_string_view_pair(p.second.data(), p.second.size()) - ); - } - return createEnumTable(make_string_view_pair(name.data(), name.size()), min_valbits, arr); - } - - inline Handle createVar( - Hierarchy::VarType vartype, - Hierarchy::VarDirection vardir, - uint32_t bitwidth, - std::string_view name, - uint32_t alias_handle - ) { - return createVar( - vartype, vardir, bitwidth, make_string_view_pair(name.data(), name.size()), alias_handle - ); - } - - inline Handle createVar2( - Hierarchy::VarType vartype, - Hierarchy::VarDirection vardir, - uint32_t bitwidth, - std::string_view name, - uint32_t alias_handle, - std::string_view type, - Hierarchy::SupplementalVarType svt, - Hierarchy::SupplementalDataType sdt - ) { - return createVar2( - vartype, - vardir, - bitwidth, - make_string_view_pair(name.data(), name.size()), - alias_handle, - make_string_view_pair(type.data(), type.size()), - svt, - sdt - ); - } -#endif private: - // File/memory buffers - // 1. For hierarchy and geometry, we do not keep the data structure, instead we just - // serialize them into buffers, and compress+write them at the end of file. - // 2. For header, we keep the data structure in memory since it is quite small - // 3. For wave data, we keep a complicated data structure in memory, - // and flush them to file when necessary - std::ofstream main_fst_file_; - std::vector hierarchy_buffer_; - std::vector geometry_buffer_; - Header header_{}; - detail::BlackoutData blackout_data_; - detail::ValueChangeData value_change_data_; - bool hierarchy_finalized_ = false; - WriterPackType pack_type_ = WriterPackType::LZ4; - uint64_t value_change_data_usage_ = 0; // Note: this value is just an estimation - uint64_t value_change_data_flush_threshold_ = 128 << 20; // 128MB - uint32_t enum_count_ = 0; - bool flush_pending_ = false; - // internal helpers static void writeHeader_(const Header &header, std::ostream &os); void appendGeometry_(std::ostream &os); void appendHierarchy_(std::ostream &os); - void appendBlackout_(std::ostream &os); + void appendBlackout_(std::ostream &os); // Not implemented yet // This function is used to flush value change data to file, and keep only the latest value in // memory Just want to separate the const part from the non-const part for code clarity static void flushValueChangeDataConstPart_( const detail::ValueChangeData &vcd, std::ostream &os, WriterPackType pack_type ); - inline void flushValueChangeData_(detail::ValueChangeData &vcd, std::ostream &os) { - if (vcd.timestamps.empty()) { + void flushValueChangeData_(detail::ValueChangeData &vcd, std::ostream &os) { + if (vcd.m_timestamps.empty()) { return; } - flushValueChangeDataConstPart_(vcd, os, pack_type_); + flushValueChangeDataConstPart_(vcd, os, m_pack_type_); vcd.keepOnlyTheLatestValue(); - ++header_.num_value_change_data_blocks; - value_change_data_usage_ = 0; - flush_pending_ = false; + ++m_header_.m_num_value_change_data_blocks; + m_value_change_data_usage_ = 0; + m_flush_pending_ = false; } void finalizeHierarchy_() { - if (hierarchy_finalized_) return; - hierarchy_finalized_ = true; + if (m_hierarchy_finalized_) return; + m_hierarchy_finalized_ = true; // Original FST code comments: as a default, use 128MB and increment when // every 1M signals are defined. - value_change_data_flush_threshold_ = (((header_.num_handles - 1) >> 20) + 1) << 27; + m_value_change_data_flush_threshold_ = (((m_header_.m_num_handles - 1) >> 20) + 1) << 27; } template void emitValueChangeHelper_(Handle handle, T &&...val); diff --git a/include/verilated_fst_c.cpp b/include/verilated_fst_c.cpp index 2ac855952..8d72b2b08 100644 --- a/include/verilated_fst_c.cpp +++ b/include/verilated_fst_c.cpp @@ -27,6 +27,7 @@ #include "verilated_fst_c.h" // Include fstcpp library +#include "fstcpp/fstcpp.h" #include "fstcpp/fstcpp_writer.h" #include @@ -178,24 +179,24 @@ void VerilatedFst::pushPrefix(const char* namep, VerilatedTracePrefixType type, m_fst->setScope(fst::Hierarchy::ScopeType::VCD_INTERFACE, name, std::string{}); break; case VerilatedTracePrefixType::STRUCT_PACKED: - m_fst->setAttrBegin(fst::AttributeType::PACK, fst::PackType::PACKED, "members", l); + m_fst->setAttrBegin(fst::Hierarchy::AttrType::PACK, fst::Hierarchy::AttrSubType::PACK_PACKED, "members", l); m_fst->setScope(fst::Hierarchy::ScopeType::VCD_STRUCT, name, std::string{}); break; case VerilatedTracePrefixType::STRUCT_UNPACKED: - m_fst->setAttrBegin(fst::AttributeType::PACK, fst::PackType::UNPACKED, "members", l); + m_fst->setAttrBegin(fst::Hierarchy::AttrType::PACK, fst::Hierarchy::AttrSubType::PACK_UNPACKED, "members", l); m_fst->setScope(fst::Hierarchy::ScopeType::VCD_STRUCT, name, std::string{}); break; case VerilatedTracePrefixType::UNION_PACKED: - m_fst->setAttrBegin(fst::AttributeType::PACK, fst::PackType::PACKED, "members", l); + m_fst->setAttrBegin(fst::Hierarchy::AttrType::PACK, fst::Hierarchy::AttrSubType::PACK_PACKED, "members", l); m_fst->setScope(fst::Hierarchy::ScopeType::VCD_UNION, name, std::string{}); break; case VerilatedTracePrefixType::ARRAY_PACKED: - m_fst->setAttrBegin(fst::AttributeType::ARRAY, fst::PackType::PACKED, "bounds", lr); - m_fst->setScope(fst::Hierarchy::ScopeType::VCD_ARRAY, name, std::string{}); + m_fst->setAttrBegin(fst::Hierarchy::AttrType::ARRAY, fst::Hierarchy::AttrSubType::ARRAY_PACKED, "bounds", lr); + m_fst->setScope(fst::Hierarchy::ScopeType::SV_ARRAY, name, std::string{}); break; case VerilatedTracePrefixType::ARRAY_UNPACKED: - m_fst->setAttrBegin(fst::AttributeType::ARRAY, fst::PackType::UNPACKED, "bounds", lr); - m_fst->setScope(fst::Hierarchy::ScopeType::VCD_ARRAY, name, std::string{}); + m_fst->setAttrBegin(fst::Hierarchy::AttrType::ARRAY, fst::Hierarchy::AttrSubType::ARRAY_UNPACKED, "bounds", lr); + m_fst->setScope(fst::Hierarchy::ScopeType::SV_ARRAY, name, std::string{}); break; default: break; }