History

Initial public commit for Dis VM

arr committed 10 years ago

Commit f95902eNo parent (first commit)40 files · +9358 −0Browse files

Showing the changes to src/vm/string.cpp (1 of 40 files). Show all

Unified · Split

src/vm/string.cpp+522−0
@@ -0,0 +1,522 @@
1+//
2+// Dis VM
3+// File: string.cpp
4+// Author: arr
5+//
6+
7+#include <cinttypes>
8+#include <runtime.h>
9+#include <utf8.h>
10+#include <vm_memory.h>
11+#include <debug.h>
12+#include <exceptions.h>
13+
14+using namespace disvm;
15+using namespace disvm::runtime;
16+
17+std::shared_ptr<const type_descriptor_t> vm_string_t::type_desc()
18+{
19+ return intrinsic_type_desc::type<vm_string_t>();
20+}
21+
22+namespace
23+{
24+ // Enumeration for character type compare cases.
25+ enum class compare_case
26+ {
27+ ascii_ascii,
28+ ascii_rune,
29+ rune_ascii,
30+ rune_rune
31+ };
32+
33+ // Scenario matrix for compare cases
34+ const compare_case scenario_matrix[][2] =
35+ {
36+ { compare_case::ascii_ascii, compare_case::ascii_rune },
37+ { compare_case::rune_ascii, compare_case::rune_rune },
38+ };
39+
40+ // Case indices
41+ const auto ascii_idx = std::size_t{ 0 };
42+ const auto rune_idx = std::size_t{ 1 };
43+
44+ // Compare function for character types.
45+ template <typename CharacterType1, typename CharacterType2>
46+ int _compare(word_t len, CharacterType1 *s1, CharacterType2 *s2)
47+ {
48+ for (auto i = word_t{ 0 }; i < len; ++i)
49+ {
50+ const auto sc1 = static_cast<rune_t>(s1[i]);
51+ const auto sc2 = static_cast<rune_t>(s2[i]);
52+
53+ if (sc1 != sc2)
54+ return (sc1 < sc2) ? (-1) : 1;
55+ }
56+
57+ return 0;
58+ }
59+}
60+
61+int vm_string_t::compare(const vm_string_t *s1, const vm_string_t *s2)
62+{
63+ auto len_1 = word_t{ 0 };
64+ auto scen_1 = ascii_idx;
65+ void *str_mem_1 = "";
66+ if (s1 != nullptr)
67+ {
68+ len_1 = s1->_length;
69+ scen_1 = s1->_character_size == sizeof(char) ? ascii_idx : rune_idx;
70+ str_mem_1 = (s1->_is_alloc) ? s1->_mem.alloc : s1->_mem.local;
71+ }
72+
73+ auto len_2 = word_t{ 0 };
74+ auto scen_2 = ascii_idx;
75+ void *str_mem_2 = "";
76+ if (s2 != nullptr)
77+ {
78+ len_2 = s2->_length;
79+ scen_2 = s2->_character_size == sizeof(char) ? ascii_idx : rune_idx;
80+ str_mem_2 = (s2->_is_alloc) ? s2->_mem.alloc : s2->_mem.local;
81+ }
82+
83+ // Check the simple compare case between the same string.
84+ if (str_mem_1 == str_mem_2)
85+ return 0;
86+
87+ const auto scenario = scenario_matrix[scen_1][scen_2];
88+
89+ auto result = int{ 0 };
90+ const auto min_length = std::min(len_1, len_2);
91+ switch (scenario)
92+ {
93+ default:
94+ assert(false && "Unknown compare scenario");
95+ case compare_case::ascii_ascii:
96+ case compare_case::rune_rune:
97+ result = std::memcmp(str_mem_1, str_mem_2, min_length);
98+ break;
99+ case compare_case::ascii_rune:
100+ result = _compare(min_length, reinterpret_cast<char *>(str_mem_1), reinterpret_cast<rune_t *>(str_mem_2));
101+ break;
102+ case compare_case::rune_ascii:
103+ result = _compare(min_length, reinterpret_cast<rune_t *>(str_mem_1), reinterpret_cast<char *>(str_mem_2));
104+ break;
105+ }
106+
107+ if (result != 0)
108+ return result;
109+
110+ // Comparing lengths if one string is a proper sub-string of the other.
111+ return (len_1 - len_2);
112+}
113+
114+namespace
115+{
116+ template<typename DestCharacterType, typename SourceCharacterType>
117+ void copy_characters_to(DestCharacterType *dest, word_t dest_start, const SourceCharacterType *source, word_t source_length)
118+ {
119+ for (auto i = word_t{ 0 }; i < source_length; ++i)
120+ dest[dest_start + i] = static_cast<DestCharacterType>(source[i]);
121+ }
122+
123+ template<>
124+ void copy_characters_to(char *dest, word_t dest_start, const char *source, word_t source_length)
125+ {
126+ assert(dest != nullptr && source != nullptr);
127+ std::memcpy((dest + dest_start), source, source_length);
128+ }
129+
130+ template<>
131+ void copy_characters_to(rune_t *dest, word_t dest_start, const rune_t *source, word_t source_length)
132+ {
133+ assert(dest != nullptr && source != nullptr);
134+ std::memcpy((dest + dest_start), source, (source_length * sizeof(rune_t)));
135+ }
136+
137+ void combine(
138+ bool dest_is_rune,
139+ uint8_t *dest,
140+ bool source1_is_rune,
141+ word_t source1_length,
142+ const uint8_t *source1,
143+ bool source2_is_rune,
144+ word_t source2_length,
145+ const uint8_t *source2)
146+ {
147+ if (dest_is_rune)
148+ {
149+ auto dest_rune = reinterpret_cast<rune_t *>(dest);
150+ if (source1_is_rune)
151+ copy_characters_to(dest_rune, 0, reinterpret_cast<const rune_t *>(source1), source1_length);
152+ else
153+ copy_characters_to(dest_rune, 0, source1, source1_length);
154+
155+ if (source2_is_rune)
156+ copy_characters_to(dest_rune, source1_length, reinterpret_cast<const rune_t *>(source2), source2_length);
157+ else
158+ copy_characters_to(dest_rune, source1_length, source2, source2_length);
159+ }
160+ else
161+ {
162+ copy_characters_to(dest, 0, source1, source1_length);
163+ copy_characters_to(dest, source1_length, source2, source2_length);
164+ }
165+ }
166+
167+ word_t compute_max_length(word_t current_length)
168+ {
169+ // Length + 1 (null) + space for string expansion
170+ return (current_length + 1 + (current_length / 4));
171+ }
172+}
173+
174+vm_string_t::vm_string_t()
175+ : vm_alloc_t(vm_string_t::type_desc())
176+ , _character_size{ sizeof(char) }
177+ , _encoded_str{ nullptr }
178+ , _is_alloc{ false }
179+ , _length{ 0 }
180+ , _length_max{ sizeof(_mem.local) }
181+ , _mem{}
182+{ }
183+
184+vm_string_t::vm_string_t(std::size_t encoded_str_len, const uint8_t *encoded_str)
185+ : vm_alloc_t(vm_string_t::type_desc())
186+ , _character_size{ sizeof(char) }
187+ , _encoded_str{ nullptr }
188+ , _is_alloc{ false }
189+ , _length{ 0 }
190+ , _length_max{ sizeof(_mem.local) }
191+ , _mem{}
192+{
193+ assert((encoded_str_len > 0 && encoded_str != nullptr) || (encoded_str_len == 0 && encoded_str == nullptr));
194+ auto character_count = utf8::count_codepoints(encoded_str, encoded_str_len);
195+ _character_size = (encoded_str_len != character_count) ? sizeof(rune_t) : sizeof(char);
196+
197+ _length = static_cast<word_t>(character_count);
198+ _length_max = sizeof(_mem.local) / _character_size;
199+
200+ // Allocate memory based on character count and size
201+ auto dest = _mem.local;
202+ if ((encoded_str_len + 1) > sizeof(_mem.local))
203+ {
204+ // If we are going to allocate, make sure it is larger than needed.
205+ _length_max = compute_max_length(_length);
206+ _mem.alloc = calloc_memory<uint8_t>(_length_max * _character_size);
207+ _is_alloc = true;
208+
209+ dest = _mem.alloc;
210+ }
211+
212+ // Copy over the entire buffer if ASCII or convert and copy if multi-byte
213+ if (_character_size == sizeof(char))
214+ {
215+ std::memcpy(dest, encoded_str, encoded_str_len);
216+ }
217+ else
218+ {
219+ auto rune_dest = reinterpret_cast<rune_t *>(dest);
220+ for (auto i = word_t{ 0 }; i < _length; ++i)
221+ encoded_str += utf8::decode(encoded_str, rune_dest[i]);
222+ }
223+
224+ if (debug::is_component_tracing_enabled<debug::component_trace_t::memory>())
225+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "init: vm string: %d %d >>%s<<\n", _length, (_length_max * _character_size), dest);
226+}
227+
228+vm_string_t::vm_string_t(const vm_string_t &s1, const vm_string_t &s2)
229+ : vm_alloc_t(vm_string_t::type_desc())
230+ , _character_size{ std::max(s1._character_size, s2._character_size) }
231+ , _encoded_str{ nullptr }
232+ , _is_alloc{ true }
233+ , _length{ s1._length + s2._length }
234+ , _length_max{ s1._length + s2._length }
235+ , _mem{}
236+{
237+ _mem.alloc = calloc_memory<uint8_t>(_length_max * _character_size);
238+
239+ const auto s1_data = (s1._is_alloc) ? s1._mem.alloc : s1._mem.local;
240+ const auto s2_data = (s2._is_alloc) ? s2._mem.alloc : s2._mem.local;
241+
242+ // Combine the two strings
243+ combine(
244+ _character_size == sizeof(rune_t),
245+ _mem.alloc,
246+ s1._character_size == sizeof(rune_t),
247+ s1._length,
248+ s1_data,
249+ s2._character_size == sizeof(rune_t),
250+ s2._length,
251+ s2_data);
252+}
253+
254+vm_string_t::vm_string_t(const vm_string_t &other, word_t begin_index, word_t end_index)
255+ : vm_alloc_t(other.alloc_type)
256+ , _character_size{ other._character_size }
257+ , _encoded_str{ nullptr }
258+ , _is_alloc{ other._is_alloc }
259+ , _length{ end_index - begin_index }
260+ , _length_max{ compute_max_length(end_index - begin_index) }
261+ , _mem{}
262+{
263+ if (end_index <= begin_index)
264+ throw out_of_range_memory{};
265+
266+ if (begin_index < 0)
267+ throw out_of_range_memory{ 0, other._length, begin_index };
268+
269+ if (other._length < end_index)
270+ throw out_of_range_memory{ 0, other._length, end_index };
271+
272+ // Allocate memory based on character count and size
273+ auto src = other._mem.local;
274+ auto dest = _mem.local;
275+ const auto length_in_bytes = _length * _character_size;
276+ if (_is_alloc)
277+ {
278+ src = other._mem.alloc;
279+ _mem.alloc = calloc_memory<uint8_t>(_length_max);
280+ dest = _mem.alloc;
281+ }
282+
283+ // Copy over string content starting at the correct index (char vs. rune_t)
284+ ::memcpy(dest, (src + (begin_index * _character_size)), length_in_bytes);
285+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "copy: vm string: %#" PRIxPTR " %d %d %#" PRIxPTR "\n", &other, begin_index, end_index, this);
286+}
287+
288+vm_string_t::~vm_string_t()
289+{
290+ // Free memory and string buffer
291+ if (_is_alloc)
292+ {
293+ if (static_cast<void *>(_mem.alloc) != static_cast<void *>(_encoded_str))
294+ free_memory(_encoded_str);
295+
296+ free_memory(_mem.alloc);
297+ }
298+ else
299+ {
300+ if (static_cast<void *>(_mem.local) != static_cast<void *>(_encoded_str))
301+ free_memory(_encoded_str);
302+ }
303+
304+ debug::log_msg(debug::component_trace_t::memory, debug::log_level_t::debug, "destroy: vm string\n");
305+
306+ debug::assign_debug_pointer(&_mem.alloc);
307+ debug::assign_debug_pointer(&_encoded_str);
308+}
309+
310+vm_string_t &vm_string_t::append(const vm_string_t &other)
311+{
312+ // This function does not adhere to the immutable string contract.
313+ // Therefore, it is unsafe to call this function if there is more
314+ // than 1 reference to this string instance.
315+ assert(get_ref_count() <= 1);
316+
317+ // Check if the new string has any length at all.
318+ const auto other_length = other._length;
319+ if (other_length == 0)
320+ return *this;
321+
322+ const auto other_is_rune = other._character_size == sizeof(rune_t);
323+ const auto other_source = (other._is_alloc) ? other._mem.alloc : other._mem.local;
324+
325+ const auto local_is_rune = _character_size == sizeof(rune_t);
326+ const auto local_length = _length;
327+ const auto local_source = (_is_alloc) ? _mem.alloc : _mem.local;
328+
329+ // Release the encoded string for this instance.
330+ {
331+ std::lock_guard<std::mutex> lock{ _encoded_str_lock };
332+ if (_encoded_str != reinterpret_cast<char*>(local_source))
333+ {
334+ free_memory(_encoded_str);
335+ _encoded_str = nullptr;
336+ }
337+ }
338+
339+ // Define the values for the appended string
340+ const auto new_length = local_length + other_length;
341+ const auto new_length_max = (new_length == 0) ? static_cast<word_t>(sizeof(_mem.local)) : compute_max_length(new_length);
342+ const auto new_is_rune = local_is_rune || other_is_rune;
343+ uint8_t *new_source = nullptr;
344+
345+ // Check if this string and the supplied string have the same character size.
346+ if (new_is_rune)
347+ new_source = calloc_memory<uint8_t>(new_length_max * sizeof(rune_t));
348+ else
349+ new_source = calloc_memory<uint8_t>(new_length_max);
350+
351+ // Combine the two string sources
352+ combine(
353+ new_is_rune,
354+ new_source,
355+ local_is_rune,
356+ local_length,
357+ local_source,
358+ other_is_rune,
359+ other_length,
360+ other_source);
361+
362+ _length = new_length;
363+ _length_max = new_length_max;
364+
365+ // Free the local allocation
366+ if (_is_alloc)
367+ {
368+ free_memory(local_source);
369+ _mem.alloc = nullptr;
370+ }
371+
372+ _mem.alloc = new_source;
373+ _is_alloc = true;
374+ _character_size = new_is_rune ? sizeof(rune_t) : sizeof(char);
375+
376+ return *this;
377+}
378+
379+word_t vm_string_t::get_length() const
380+{
381+ return _length;
382+}
383+
384+rune_t vm_string_t::get_rune(word_t index) const
385+{
386+ if (index < 0 && _length <= index)
387+ throw out_of_range_memory{ 0, _length - 1, index };
388+
389+ // Determine if local or allocated memory
390+ auto source = reinterpret_cast<const char *>(_mem.local);
391+ if (_is_alloc)
392+ source = reinterpret_cast<const char *>(_mem.alloc);
393+
394+ // Check if multi-byte or ASCII
395+ if (_character_size != sizeof(char))
396+ return reinterpret_cast<const rune_t *>(source)[index];
397+ else
398+ return static_cast<const rune_t>(source[index]);
399+}
400+
401+void vm_string_t::set_rune(word_t index, rune_t value)
402+{
403+ // This function does not adhere to the immutable string contract.
404+ // Therefore, it is unsafe to call this function if there is more
405+ // than 1 reference to this string instance.
406+ assert(get_ref_count() <= 1);
407+
408+ if (value > utf8::constants::max_supported_codepoint)
409+ throw invalid_utf8{};
410+
411+ // Index equal to length is allowed to grow the string
412+ if (index < 0 && _length <= index)
413+ throw out_of_range_memory{ 0, _length, index };
414+
415+ // Determine if local or allocated memory
416+ auto source = static_cast<uint8_t *>(_mem.local);
417+ if (_is_alloc)
418+ {
419+ source = static_cast<uint8_t *>(_mem.alloc);
420+
421+ // Since we are setting a rune, the string value should be freed.
422+ if (_encoded_str != nullptr)
423+ free_memory(_encoded_str);
424+ }
425+
426+ // Reset the string so it can be regenerated.
427+ _encoded_str = nullptr;
428+
429+ const auto new_character_size = (value > utf8::constants::max_codepoint_ascii) ? sizeof(rune_t) : sizeof(char);
430+ assert(new_character_size < std::numeric_limits<uint8_t>::max());
431+
432+ if (new_character_size > _character_size) // Expand the string if the new character is non-ASCII.
433+ {
434+ _character_size = static_cast<uint8_t>(new_character_size);
435+ assert(_character_size == sizeof(rune_t));
436+
437+ _length_max = (_length == 0) ? static_cast<word_t>(sizeof(_mem.local)) : compute_max_length(_length);
438+ auto new_alloc = calloc_memory<rune_t>(_length_max * _character_size);
439+
440+ // Copy source into the new alloc.
441+ copy_characters_to(new_alloc, 0, source, _length);
442+
443+ // Free memory, if allocated
444+ if (_is_alloc)
445+ free_memory(source);
446+
447+ debug::assign_debug_memory(&_mem, sizeof(_mem));
448+
449+ _is_alloc = true;
450+ _mem.alloc = reinterpret_cast<uint8_t *>(new_alloc);
451+ source = _mem.alloc;
452+ }
453+ else if (static_cast<word_t>(_length_max) <= index) // Check if string can accomodate the new character.
454+ {
455+ _length_max = (_length == 0) ? static_cast<word_t>(sizeof(_mem.local)) : compute_max_length(_length);
456+ auto new_alloc = calloc_memory<uint8_t>(_length_max * _character_size);
457+ ::memcpy(new_alloc, source, _length * _character_size);
458+
459+ // Free memory, if allocated
460+ if (_is_alloc)
461+ free_memory(source);
462+
463+ debug::assign_debug_memory(&_mem, sizeof(_mem));
464+
465+ _is_alloc = true;
466+ _mem.alloc = new_alloc;
467+ source = _mem.alloc;
468+ }
469+
470+ // Check if multi-byte or ASCII.
471+ if (_character_size != sizeof(char))
472+ reinterpret_cast<rune_t *>(source)[index] = value;
473+ else
474+ source[index] = static_cast<uint8_t>(value & utf8::constants::max_codepoint_ascii);
475+
476+ // Increment the length, if the index matches the length.
477+ if (_length == index)
478+ _length++;
479+}
480+
481+int vm_string_t::compare_to(const vm_string_t *s) const
482+{
483+ return vm_string_t::compare(this, s);
484+}
485+
486+const char *vm_string_t::str() const
487+{
488+ if (_encoded_str != nullptr)
489+ return _encoded_str;
490+
491+ {
492+ // [PERF] Use a spin-lock instead of a mutex here.
493+ std::lock_guard<std::mutex> lock{ _encoded_str_lock };
494+
495+ if (_encoded_str != nullptr)
496+ return _encoded_str;
497+
498+ // If the string is ASCII then just return the memory
499+ if (_character_size == sizeof(char))
500+ {
501+ _encoded_str = (_is_alloc) ? reinterpret_cast<char *>(_mem.alloc) : reinterpret_cast<char *>(_mem.local);
502+ return _encoded_str;
503+ }
504+
505+ const auto rune_str = (_is_alloc) ? reinterpret_cast<rune_t *>(_mem.alloc) : reinterpret_cast<rune_t *>(_mem.local);
506+ const auto str_len = (_length * _character_size) + 1; // Plus 1 for a null terminator
507+ auto encoded_str_local = alloc_memory<char>(str_len);
508+
509+ auto s = reinterpret_cast<uint8_t *>(encoded_str_local);
510+ for (auto i = word_t{ 0 }; i < _length; ++i)
511+ {
512+ auto curr_rune = rune_str[i];
513+ s = utf8::encode(curr_rune, s);
514+ }
515+
516+ // Ensure the final value is null.
517+ *s = 0;
518+
519+ _encoded_str = encoded_str_local;
520+ return _encoded_str;
521+ }
522+}