OXYGENHUB
◆ main openmsx / src / debugger / TraceValue.hh
7.9 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#ifndef TRACE_VALUE_HH
2
#define TRACE_VALUE_HH
3
4
// TODO unittest
5
6
#include "one_of.hh"
7
#include "zstring_view.hh"
8
9
#include <algorithm>
10
#include <array>
11
#include <cassert>
12
#include <concepts>
13
#include <cstdint>
14
#include <cstring>
15
#include <optional>
16
#include <utility>
17
18
// Stores a trace-value (void, int, double, string) in a memory efficient way.
19
// Goal is to be able to store thousands or even a few million of these objects
20
// without consuming a ton of memory.
21
//
22
// The current solution requires 16 bytes per value. Except for strings longer
23
// than 15 characters we need an additional heap-allocation equal to the size of
24
// that string plus one byte.
25
//
26
// Internally we have an array of 16 bytes. The last byte is a tag-byte
27
// indicating the type of the stored value. Small strings up to 15 characters
28
// are stored directly in this array, for long strings we instead store a
29
// pointer to a heap allocated string. In both cases the string is
30
// zero-terminated (tradeoff: allows to omit storing the length of the string,
31
// but we cannot store strings with embedded zeros).
32
//
33
// In case of a 15 character short string, the zero terminator would overwrite
34
// the tag-byte. For that reason we've choosen tag-value = 0 to mean "short
35
// string".
36
37
static constexpr auto TraceValueAlignment = std::max({
38
	alignof(char*), alignof(uint64_t), alignof(double)});
39
struct alignas(TraceValueAlignment) TraceValue {
40
public:
41
	enum class Tag : char {
42
		SSO = 0, // small string, raw[15]==0 acts as NUL terminator
43
		HeapStr = 1, // owning heap-allocated NUL-terminated string
44
		Monostate = 2, // no value (void)
45
		U64 = 3, // some integer (up to 64 bits), or bool
46
		Double = 4, // floating point
47
	};
48
49
	TraceValue() = default; // empty string (zero-filled payload, tag==SSO)
50
51
	TraceValue(std::string_view s)   { set_string(s); }
52
	TraceValue(std::monostate)       { set_monostate(); }
53
	TraceValue(std::integral auto x) { set_u64(uint64_t(x)); }
54
	TraceValue(double d)             { set_double(d); }
55
56
	TraceValue(const TraceValue& other) {
57
		if (other.tag() == Tag::HeapStr) {
58
			const char* src = other.read_payload<const char*>();
59
			assert(src != nullptr);
60
			size_t len = std::strlen(src);
61
			char* dst = new char[len + 1];
62
			memcpy(dst, src, len + 1);
63
			write_payload(dst);
64
		} else {
65
			memcpy(raw.data(), other.raw.data(), 16);
66
		}
67
	}
68
69
	TraceValue(TraceValue&& other) noexcept {
70
		memcpy(raw.data(), other.raw.data(), 16);
71
		other.raw.fill(0);
72
	}
73
74
	TraceValue& operator=(const TraceValue& other) {
75
		TraceValue tmp(other);
76
		swap(tmp);
77
		return *this;
78
	}
79
80
	TraceValue& operator=(TraceValue&& other) noexcept {
81
		swap(other);
82
		return *this;
83
	}
84
85
	~TraceValue() noexcept { cleanup(); }
86
87
	void swap(TraceValue& other) noexcept { raw.swap(other.raw); }
88
89
	TraceValue& operator=(std::string_view s) { set_string(s); return *this; }
90
	TraceValue& operator=(const char* s)      { set_string(std::string_view(s)); return *this; }
91
	TraceValue& operator=(std::monostate)     { set_monostate(); return *this; }
92
	TraceValue& operator=(uint64_t x)         { set_u64(x); return *this; }
93
	TraceValue& operator=(double d)           { set_double(d); return *this; }
94
95
	bool operator==(const TraceValue& other) const {
96
		 // must be the same type, e.g. integer 123 and string "123" don't match
97
		if (tag() != other.tag()) return false;
98
99
		switch (tag()) {
100
		case Tag::SSO:
101
		case Tag::HeapStr:   return get_string() == other.get_string();
102
		case Tag::Monostate: return true;
103
		case Tag::U64:       return get_u64()    == other.get_u64();
104
		case Tag::Double:    return get_double() == other.get_double();
105
		default: std::unreachable();
106
		}
107
	}
108
109
	[[nodiscard]] size_t index() const { return std::to_underlying(tag()); }
110
111
	template<typename T> [[nodiscard]] bool holds_alternative() const = delete;
112
	template<typename T> [[nodiscard]] T get() const = delete;
113
	template<typename T> [[nodiscard]] std::optional<T> get_if() const = delete;
114
115
	template<typename Visitor>
116
	decltype(auto) visit(Visitor&& vis) const {
117
		switch (tag()) {
118
		case Tag::SSO:
119
		case Tag::HeapStr:   return std::forward<Visitor>(vis)(get_string());
120
		case Tag::Monostate: return std::forward<Visitor>(vis)(get_monostate());
121
		case Tag::U64:       return std::forward<Visitor>(vis)(get_u64());
122
		case Tag::Double:    return std::forward<Visitor>(vis)(get_double());
123
		default: std::unreachable();
124
		}
125
	}
126
127
	void set_string(std::string_view sv) {
128
		cleanup();
129
		size_t len = sv.size();
130
		if (len <= (raw.size() - 1)) {
131
			memcpy(raw.data(), sv.data(), len);
132
			raw[len] = 0; // possibly this overlaps with the tag byte, that's okay
133
			set_tag(Tag::SSO); // Tag::SSO == 0
134
		} else {
135
			char* p = new char[len + 1];
136
			memcpy(p, sv.data(), len);
137
			p[len] = '\0';
138
			write_payload<char*>(p);
139
			set_tag(Tag::HeapStr);
140
		}
141
	}
142
	[[nodiscard]] zstring_view get_string() const {
143
		if (tag() == Tag::SSO) {
144
			return {raw.data()};
145
		} else {
146
			assert(tag() == Tag::HeapStr);
147
			return {read_payload<const char*>()};
148
		}
149
	}
150
151
	void set_monostate() {
152
		set_tag(Tag::Monostate);
153
	}
154
	[[nodiscard]] std::monostate get_monostate() const {
155
		assert(tag() == Tag::Monostate);
156
		return {};
157
	}
158
159
	void set_u64(uint64_t v) {
160
		cleanup();
161
		write_payload<uint64_t>(v);
162
		set_tag(Tag::U64);
163
	}
164
	[[nodiscard]] uint64_t get_u64() const {
165
		assert(tag() == Tag::U64);
166
		return read_payload<uint64_t>();
167
	}
168
169
	void set_double(double d) {
170
		cleanup();
171
		write_payload<double>(d);
172
		set_tag(Tag::Double);
173
	}
174
	[[nodiscard]] double get_double() const {
175
		assert(tag() == Tag::Double);
176
		return read_payload<double>();
177
	}
178
179
	[[nodiscard]] bool get_as_bool() const {
180
		// empty string, zero integer, zero double are all considered
181
		// false, everything else is true
182
		switch (tag()) {
183
		case Tag::SSO:
184
		case Tag::HeapStr:   return !get_string().empty();
185
		case Tag::Monostate: return false;
186
		case Tag::U64:       return get_u64() != 0;
187
		case Tag::Double:    return get_double() != 0.0;
188
		default: std::unreachable();
189
		}
190
	}
191
192
private:
193
	[[nodiscard]] Tag tag() const { return Tag(raw.back()); }
194
	void set_tag(Tag t) { raw.back() = char(t); }
195
196
	// Helper templates to read/write typed values from the payload bytes.
197
	template<typename T> [[nodiscard]] T read_payload() const {
198
		T tmp{};
199
		memcpy(static_cast<void*>(&tmp), raw.data(), sizeof(T));
200
		return tmp;
201
	}
202
	template<typename T> void write_payload(const T& v) {
203
		memcpy(raw.data(), static_cast<const void*>(&v), sizeof(T));
204
	}
205
206
	void cleanup() {
207
		if (tag() == Tag::HeapStr) {
208
			delete[] read_payload<char*>();
209
		}
210
	}
211
212
private:
213
	std::array<char, 16> raw = {}; // raw[0..14] payload, raw[15] = tag
214
};
215
216
217
// explicit holds_alternative specializations
218
template<> inline bool TraceValue::holds_alternative<zstring_view>() const {
219
	return tag() == one_of(Tag::SSO, Tag::HeapStr);
220
}
221
template<> inline bool TraceValue::holds_alternative<std::monostate>() const {
222
	return tag() == Tag::Monostate;
223
}
224
template<> inline bool TraceValue::holds_alternative<uint64_t>() const {
225
	return tag() == Tag::U64;
226
}
227
template<> inline bool TraceValue::holds_alternative<double>() const {
228
	return tag() == Tag::Double;
229
}
230
231
// explicit get<T> specializations
232
template<> inline zstring_view TraceValue::get<zstring_view>() const {
233
	return get_string();
234
}
235
template<> inline std::monostate TraceValue::get<std::monostate>() const {
236
	return get_monostate();
237
}
238
template<> inline uint64_t TraceValue::get<uint64_t>() const {
239
	return get_u64();
240
}
241
template<> inline double TraceValue::get<double>() const {
242
	return get_double();
243
}
244
245
// explicit get_if<T> specializations
246
template<> inline std::optional<zstring_view> TraceValue::get_if<zstring_view>() const {
247
	if (tag() == one_of(Tag::SSO, Tag::HeapStr)) return get_string();
248
	return {};
249
}
250
template<> inline std::optional<std::monostate> TraceValue::get_if<std::monostate>() const {
251
	if (tag() == Tag::Monostate) return get_monostate();
252
	return {};
253
}
254
template<> inline std::optional<uint64_t> TraceValue::get_if<uint64_t>() const {
255
	if (tag() == Tag::U64) return get_u64();
256
	return {};
257
}
258
template<> inline std::optional<double> TraceValue::get_if<double>() const {
259
	if (tag() == Tag::Double) return get_double();
260
	return {};
261
}
262
263
#endif