Fix regression error and review comments

This commit is contained in:
Yu-Sheng Lin 2026-03-16 02:20:32 +08:00
parent 28a2b53211
commit e97243f173
8 changed files with 560 additions and 689 deletions

View File

@ -10,7 +10,7 @@
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <utility>
#include <string>
// 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<const char *, std::size_t>;
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);

View File

@ -6,9 +6,9 @@
// direct include
// C system headers
// C++ standard library headers
#include <cstdlib>
#include <iostream>
#include <sstream>
#include <stdexcept>
// 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.

View File

@ -6,10 +6,12 @@
#pragma once
// direct include
// C system headers
#ifdef _MSC_VER
# include <intrin.h>
#endif
// C++ standard library headers
#include <cstdint>
#include <cstring>
#include <iostream>
#include <vector>
// Other libraries' .h files.
// Your project's .h files.
@ -26,10 +28,27 @@ namespace platform {
// clang-format off
template <typename U> U to_big_endian(U u) { return u; }
#else
#if defined(__GNUC__) || defined(__clang__)
template<typename U> U to_big_endian(U u, std::integral_constant<size_t, 1>) { return u; }
template<typename U> U to_big_endian(U u, std::integral_constant<size_t, 2>) { return __builtin_bswap16(u); }
template<typename U> U to_big_endian(U u, std::integral_constant<size_t, 4>) { return __builtin_bswap32(u); }
template<typename U> U to_big_endian(U u, std::integral_constant<size_t, 8>) { return __builtin_bswap64(u); }
#elif defined(_MSC_VER) // MSVC
template<typename U> U to_big_endian(U u, std::integral_constant<size_t, 1>) { return u; }
template<typename U> U to_big_endian(U u, std::integral_constant<size_t, 2>) { return _byteswap_ushort(u); }
template<typename U> U to_big_endian(U u, std::integral_constant<size_t, 4>) { return _byteswap_ulong(u); }
template<typename U> U to_big_endian(U u, std::integral_constant<size_t, 8>) { return _byteswap_uint64(u); }
#else
template<typename U, size_t S> U to_big_endian(U u, std::integral_constant<size_t, S>) {
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 <typename U>
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 <typename U>
StreamWriteHelper &writeUInt(U u) {
u = platform::to_big_endian(u);
os->write(reinterpret_cast<const char *>(&u), sizeof(u));
m_os->write(reinterpret_cast<const char *>(&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<const char *>(&u), (bitwidth + 7) / 8);
m_os->write(reinterpret_cast<const char *>(&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<const char *>(buf), len);
len = static_cast<int>(pnt - buf);
m_os->write(reinterpret_cast<const char *>(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<unsigned char>(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<const char *>(buf), len);
len = static_cast<int>(pnt - buf);
m_os->write(reinterpret_cast<const char *>(buf), len);
return *this;
}
template <typename F>
StreamWriteHelper &writeFloat(F f) {
// Always write in native endianness
os->write(reinterpret_cast<const char *>(&f), sizeof(f));
m_os->write(reinterpret_cast<const char *>(&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<const char *>(ptr), size);
m_os->write(reinterpret_cast<const char *>(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<uint8_t> &vec;
std::vector<uint8_t> &m_vec;
StreamVectorWriteHelper(std::vector<uint8_t> &vec_) : vec(vec_) {}
StreamVectorWriteHelper(std::vector<uint8_t> &vec_) : m_vec{vec_} {}
template <typename T>
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 <typename T>
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 <typename T>
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 <typename E>
StreamVectorWriteHelper &writeU8Enum(E e) {
vec.push_back(static_cast<uint8_t>(e));
m_vec.push_back(static_cast<uint8_t>(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<int>(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<unsigned char>(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<int>(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;
}

View File

@ -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<uint32_t>(platform::clog2(new_size)),
static_cast<uint32_t>(kCapacityBaseShift)
) -
static_cast<uint32_t>(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);
}

View File

@ -7,6 +7,9 @@
// direct include
#include "fstcpp/fstcpp.h"
// C system headers
#ifdef _MSC_VER
# include <intrin.h>
#endif
// C++ standard library headers
#include <algorithm>
#include <cstdint>
@ -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<uint64_t>(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<uint32_t>(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<uint32_t>(encoding_), kLastEncodingTypeWidth, kLastEncodingTypeOffset
m_misc,
static_cast<uint32_t>(encoding_),
kLastEncodingTypeWidth,
kLastEncodingTypeOffset
);
}
inline EncodingType last_written_encode_type() const {
EncodingType last_written_encode_type() const {
return static_cast<EncodingType>(
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<double> here since the uint64_t is
// already bit_cast'ed from double
wh.write<uint64_t>(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 <typename Callable, typename... Args>
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<uint8_t>(const_cast<VariableInfo &>(*this)),
std::forward<Args>(args)...
);
// } else if (bitwidth <= 16) {
// return
// callable(detail::VariableInfoScalarInt<uint16_t>(const_cast<VariableInfo&>(*this)),
// std::forward<Args>(args)...); } else if (bitwidth <= 32) { return
// callable(detail::VariableInfoScalarInt<uint32_t>(const_cast<VariableInfo&>(*this)),
// std::forward<Args>(args)...);
} else if (bitwidth <= 64) {
return callable(
detail::VariableInfoScalarInt<uint64_t>(const_cast<VariableInfo &>(*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<uint32_t>(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<uint32_t>(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<uint32_t>(size() - old_size);
}
inline void VariableInfo::dumpInitialBits(std::vector<uint8_t> &buf) const {
@ -801,7 +812,7 @@ inline void VariableInfo::dumpValueChanges(std::vector<uint8_t> &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); });
}

View File

@ -9,9 +9,7 @@
// C++ standard library headers
#include <cstdio>
#include <cstring>
#include <iostream>
#include <numeric>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <utility>
@ -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<uint8_t>(enable).writeLEB128(current_timestamp - previous_timestamp);
++count;
StreamVectorWriteHelper h(m_buffer);
h.writeUIntBE<uint8_t>(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<uint32_t>(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 <typename T, typename... U>
uint64_t emitValueHelperStaticDispatch_(
VariableInfo *var_info, const uint64_t time_index, U &&...val
) {
return static_cast<T *>(var_info)->emitValueChange(time_index, std::forward<U>(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 <typename... T>
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<T>(val)...);
m_value_change_data_usage_ += var_info.emitValueChange(
m_value_change_data_.m_timestamps.size() - 1, std::forward<T>(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<uint64_t> packed_value_buffer;
const unsigned num_words = (bitwidth + 63) / 64;
packed_value_buffer.assign(num_words, 0);
thread_local static std::vector<uint64_t> 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<uint64_t>(*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<uint8_t>(header.filetype))
.writeUInt(header.timezero);
.writeUInt(static_cast<uint8_t>(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<uint8_t> &uncompressed_data, vector<uint8_t> &compressed_data) {
void compressUsingLz4(
const std::vector<uint8_t> &uncompressed_data, std::vector<uint8_t> &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<uint8_t> &uncompressed_data, vector<uint8_t>
}
void compressUsingZlib(
const vector<uint8_t> &uncompressed_data, vector<uint8_t> &compressed_data, int level
const std::vector<uint8_t> &uncompressed_data, std::vector<uint8_t> &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<const uint8_t *, size_t> selectSmaller(
const vector<uint8_t> &compressed_data, const vector<uint8_t> &uncompressed_data
std::pair<const uint8_t *, size_t> selectSmaller(
const std::vector<uint8_t> &compressed_data, const std::vector<uint8_t> &uncompressed_data
) {
pair<const uint8_t *, size_t> ret;
std::pair<const uint8_t *, size_t> ret;
if (compressed_data.size() < uncompressed_data.size()) {
ret.first = compressed_data.data();
ret.second = compressed_data.size();
@ -383,103 +380,109 @@ pair<const uint8_t *, size_t> 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<uint8_t> geometry_buffer_compressed_;
compressUsingZlib(geometry_buffer_, geometry_buffer_compressed_, 9);
std::vector<uint8_t> 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<const uint8_t *, size_t> 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<uint64_t>(geometry_buffer_.size())
.writeUInt<uint64_t>(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<uint8_t> hierarchy_buffer_compressed_(compressed_bound);
const int compressed_size = LZ4_compress_default(
reinterpret_cast<const char *>(hierarchy_buffer_.data()),
const int compressed_bound{LZ4_compressBound(m_hierarchy_buffer_.size())};
std::vector<uint8_t> hierarchy_buffer_compressed_(compressed_bound);
const int compressed_size{LZ4_compress_default(
reinterpret_cast<const char *>(m_hierarchy_buffer_.data()),
reinterpret_cast<char *>(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<uint64_t>(hierarchy_buffer_.size())
.writeUInt<uint64_t>(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<uint8_t> &blackout_data = blackout_data_.buffer;
const auto begin_of_blackout_block = os.tellp();
const std::vector<uint8_t> &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<uint64_t>(size_of_blackout_block - kSharedBlockHeaderSize)
);
}
void detail::ValueChangeData::writeInitialBits(vector<uint8_t> &os) const {
void detail::ValueChangeData::writeInitialBits(std::vector<uint8_t> &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<vector<uint8_t>> detail::ValueChangeData::computeWaveData() const {
const size_t N = variable_infos.size();
vector<vector<uint8_t>> data(N);
for (size_t i = 0; i < N; ++i) {
variable_infos[i].dumpValueChanges(data[i]);
std::vector<std::vector<uint8_t>> detail::ValueChangeData::computeWaveData() const {
const size_t N{m_variable_infos.size()};
std::vector<std::vector<uint8_t>> data(N);
for (size_t i{0}; i < N; ++i) {
m_variable_infos[i].dumpValueChanges(data[i]);
}
return data;
}
vector<int64_t> detail::ValueChangeData::uniquifyWaveData(vector<vector<uint8_t>> &data) {
std::vector<int64_t> detail::ValueChangeData::uniquifyWaveData(
std::vector<std::vector<uint8_t>> &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<int64_t> positions(data.size(), 0);
std::vector<int64_t> positions(data.size(), 0);
struct MyHash {
size_t operator()(const vector<uint8_t> *vec) const {
size_t operator()(const std::vector<uint8_t> *vec) const {
size_t seed = 0;
for (auto v : *vec) {
seed ^= v + 0x9e3779b9 + (seed << 6) + (seed >> 2);
@ -488,11 +491,11 @@ vector<int64_t> detail::ValueChangeData::uniquifyWaveData(vector<vector<uint8_t>
}
};
struct MyEqual {
bool operator()(const vector<uint8_t> *a, const vector<uint8_t> *b) const {
bool operator()(const std::vector<uint8_t> *a, const std::vector<uint8_t> *b) const {
return *a == *b;
}
};
unordered_map<const vector<uint8_t> *, int64_t, MyHash, MyEqual> data_map;
std::unordered_map<const std::vector<uint8_t> *, 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<int64_t> detail::ValueChangeData::uniquifyWaveData(vector<vector<uint8_t>
// insert vec->i to data_map if not exists
auto p = data_map.emplace(&data[i], static_cast<int64_t>(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<int64_t> detail::ValueChangeData::uniquifyWaveData(vector<vector<uint8_t>
}
uint64_t detail::ValueChangeData::encodePositionsAndwriteUniqueWaveData(
ostream &os,
const vector<vector<uint8_t>> &data,
vector<int64_t> &positions,
std::ostream &os,
const std::vector<std::vector<uint8_t>> &data,
std::vector<int64_t> &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<uint8_t> compressed_data;
std::vector<uint8_t> 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<const uint8_t *, size_t> 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<int64_t> &encoded_positions, ostream &os
const std::vector<int64_t> &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<uint8_t> &os) const {
void detail::ValueChangeData::writeTimestamps(std::vector<uint8_t> &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<uint64_t>(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<uint8_t> bits_data;
std::vector<uint8_t> bits_data;
vcd.writeInitialBits(bits_data);
vector<uint8_t> bits_data_compressed;
std::vector<uint8_t> 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<const uint8_t *, size_t> 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<std::vector<uint8_t>> 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<uint8_t> &b) { return a + b.size(); }
)};
std::vector<int64_t> 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<int64_t> 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<uint64_t>(os.tellp() - pos_begin)};
h.writeUInt(pos_size); // Length comes AFTER data for positions
}
// 5. Time Section
{
vector<uint8_t> time_data;
std::vector<uint8_t> time_data;
vcd.writeTimestamps(time_data);
vector<uint8_t> time_data_compressed;
std::vector<uint8_t> 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<const uint8_t *, size_t> 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<uint64_t>(end_pos - start_pos - 1)
.seek(start_pos + std::streamoff(1), std::ios_base::beg)
.writeUInt<uint64_t>(static_cast<uint64_t>(end_pos - start_pos - 1))
// Patch Memory Required
.seek(memory_usage_pos, ios_base::beg)
.writeUInt<uint64_t>(memory_usage)
.seek(memory_usage_pos, std::ios_base::beg)
.writeUInt<uint64_t>(static_cast<uint64_t>(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<pair<string_view_pair, string_view_pair>> &literal_val_arr
const std::vector<std::pair<string_view_pair, string_view_pair>> &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,

View File

@ -12,7 +12,6 @@
#include <cstdint>
#include <ctime>
#include <fstream>
#include <string>
#include <vector>
#if __cplusplus >= 201703L
# include <string_view>
@ -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<uint8_t> buffer;
uint64_t previous_timestamp = 0;
uint64_t count = 0;
std::vector<uint8_t> 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<VariableInfo> variable_infos;
std::vector<uint64_t> timestamps;
std::vector<VariableInfo> m_variable_infos{};
std::vector<uint64_t> 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<uint8_t> m_hierarchy_buffer_{};
std::vector<uint8_t> 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<uint8_t>(Hierarchy::ScopeControlType::GEN_ATTR_END)
);
}
@ -131,7 +158,20 @@ public:
uint32_t min_valbits,
const std::vector<std::pair<string_view_pair, string_view_pair>> &literal_val_arr
);
inline void emitEnumTableRef(EnumHandle handle) {
template <typename T1, typename T2>
EnumHandle createEnumTable(
const char *name,
uint32_t min_valbits,
const std::vector<std::pair<T1, T2>> &literal_val_arr
) {
std::vector<std::pair<string_view_pair, string_view_pair>> 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<void *>(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<std::pair<const char *, const char *>> &literal_val_arr
) {
std::vector<std::pair<string_view_pair, string_view_pair>> 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<std::pair<std::string_view, std::string_view>> &literal_val_arr
) {
std::vector<std::pair<string_view_pair, string_view_pair>> 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<uint8_t> hierarchy_buffer_;
std::vector<uint8_t> 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 <typename... T>
void emitValueChangeHelper_(Handle handle, T &&...val);

View File

@ -27,6 +27,7 @@
#include "verilated_fst_c.h"
// Include fstcpp library
#include "fstcpp/fstcpp.h"
#include "fstcpp/fstcpp_writer.h"
#include <algorithm>
@ -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;
}