1 | // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
2 | // for details. All rights reserved. Use of this source code is governed by a |
3 | // BSD-style license that can be found in the LICENSE file. |
4 | |
5 | #include "vm/symbols.h" |
6 | |
7 | #include "platform/unicode.h" |
8 | #include "vm/handles.h" |
9 | #include "vm/hash_table.h" |
10 | #include "vm/heap/safepoint.h" |
11 | #include "vm/isolate.h" |
12 | #include "vm/object.h" |
13 | #include "vm/object_store.h" |
14 | #include "vm/raw_object.h" |
15 | #include "vm/reusable_handles.h" |
16 | #include "vm/snapshot_ids.h" |
17 | #include "vm/type_table.h" |
18 | #include "vm/visitor.h" |
19 | |
20 | namespace dart { |
21 | |
22 | StringPtr Symbols::predefined_[Symbols::kNumberOfOneCharCodeSymbols]; |
23 | String* Symbols::symbol_handles_[Symbols::kMaxPredefinedId]; |
24 | |
25 | static const char* names[] = { |
26 | // clang-format off |
27 | NULL, |
28 | #define DEFINE_SYMBOL_LITERAL(symbol, literal) literal, |
29 | PREDEFINED_SYMBOLS_LIST(DEFINE_SYMBOL_LITERAL) |
30 | #undef DEFINE_SYMBOL_LITERAL |
31 | "" , // matches kTokenTableStart. |
32 | #define DEFINE_TOKEN_SYMBOL_INDEX(t, s, p, a) s, |
33 | DART_TOKEN_LIST(DEFINE_TOKEN_SYMBOL_INDEX) |
34 | DART_KEYWORD_LIST(DEFINE_TOKEN_SYMBOL_INDEX) |
35 | #undef DEFINE_TOKEN_SYMBOL_INDEX |
36 | // clang-format on |
37 | }; |
38 | |
39 | StringPtr StringFrom(const uint8_t* data, intptr_t len, Heap::Space space) { |
40 | return String::FromLatin1(data, len, space); |
41 | } |
42 | |
43 | StringPtr StringFrom(const uint16_t* data, intptr_t len, Heap::Space space) { |
44 | return String::FromUTF16(data, len, space); |
45 | } |
46 | |
47 | StringPtr StringFrom(const int32_t* data, intptr_t len, Heap::Space space) { |
48 | return String::FromUTF32(data, len, space); |
49 | } |
50 | |
51 | template <typename CharType> |
52 | class CharArray { |
53 | public: |
54 | CharArray(const CharType* data, intptr_t len) : data_(data), len_(len) { |
55 | hash_ = String::Hash(data, len); |
56 | } |
57 | StringPtr ToSymbol() const { |
58 | String& result = String::Handle(StringFrom(data_, len_, Heap::kOld)); |
59 | result.SetCanonical(); |
60 | result.SetHash(hash_); |
61 | return result.raw(); |
62 | } |
63 | bool Equals(const String& other) const { |
64 | ASSERT(other.HasHash()); |
65 | if (other.Hash() != hash_) { |
66 | return false; |
67 | } |
68 | return other.Equals(data_, len_); |
69 | } |
70 | intptr_t Hash() const { return hash_; } |
71 | |
72 | private: |
73 | const CharType* data_; |
74 | intptr_t len_; |
75 | intptr_t hash_; |
76 | }; |
77 | typedef CharArray<uint8_t> Latin1Array; |
78 | typedef CharArray<uint16_t> UTF16Array; |
79 | typedef CharArray<int32_t> UTF32Array; |
80 | |
81 | class StringSlice { |
82 | public: |
83 | StringSlice(const String& str, intptr_t begin_index, intptr_t length) |
84 | : str_(str), begin_index_(begin_index), len_(length) { |
85 | hash_ = is_all() ? str.Hash() : String::Hash(str, begin_index, length); |
86 | } |
87 | StringPtr ToSymbol() const; |
88 | bool Equals(const String& other) const { |
89 | ASSERT(other.HasHash()); |
90 | if (other.Hash() != hash_) { |
91 | return false; |
92 | } |
93 | return other.Equals(str_, begin_index_, len_); |
94 | } |
95 | intptr_t Hash() const { return hash_; } |
96 | |
97 | private: |
98 | bool is_all() const { return begin_index_ == 0 && len_ == str_.Length(); } |
99 | const String& str_; |
100 | intptr_t begin_index_; |
101 | intptr_t len_; |
102 | intptr_t hash_; |
103 | }; |
104 | |
105 | StringPtr StringSlice::ToSymbol() const { |
106 | if (is_all() && str_.IsOld()) { |
107 | str_.SetCanonical(); |
108 | return str_.raw(); |
109 | } else { |
110 | String& result = |
111 | String::Handle(String::SubString(str_, begin_index_, len_, Heap::kOld)); |
112 | result.SetCanonical(); |
113 | result.SetHash(hash_); |
114 | return result.raw(); |
115 | } |
116 | } |
117 | |
118 | class ConcatString { |
119 | public: |
120 | ConcatString(const String& str1, const String& str2) |
121 | : str1_(str1), str2_(str2), hash_(String::HashConcat(str1, str2)) {} |
122 | StringPtr ToSymbol() const; |
123 | bool Equals(const String& other) const { |
124 | ASSERT(other.HasHash()); |
125 | if (other.Hash() != hash_) { |
126 | return false; |
127 | } |
128 | return other.EqualsConcat(str1_, str2_); |
129 | } |
130 | intptr_t Hash() const { return hash_; } |
131 | |
132 | private: |
133 | const String& str1_; |
134 | const String& str2_; |
135 | intptr_t hash_; |
136 | }; |
137 | |
138 | StringPtr ConcatString::ToSymbol() const { |
139 | String& result = String::Handle(String::Concat(str1_, str2_, Heap::kOld)); |
140 | result.SetCanonical(); |
141 | result.SetHash(hash_); |
142 | return result.raw(); |
143 | } |
144 | |
145 | class SymbolTraits { |
146 | public: |
147 | static const char* Name() { return "SymbolTraits" ; } |
148 | static bool ReportStats() { return false; } |
149 | |
150 | static bool IsMatch(const Object& a, const Object& b) { |
151 | const String& a_str = String::Cast(a); |
152 | const String& b_str = String::Cast(b); |
153 | ASSERT(a_str.HasHash()); |
154 | ASSERT(b_str.HasHash()); |
155 | if (a_str.Hash() != b_str.Hash()) { |
156 | return false; |
157 | } |
158 | intptr_t a_len = a_str.Length(); |
159 | if (a_len != b_str.Length()) { |
160 | return false; |
161 | } |
162 | // Use a comparison which does not consider the state of the canonical bit. |
163 | return a_str.Equals(b_str, 0, a_len); |
164 | } |
165 | template <typename CharType> |
166 | static bool IsMatch(const CharArray<CharType>& array, const Object& obj) { |
167 | return array.Equals(String::Cast(obj)); |
168 | } |
169 | static bool IsMatch(const StringSlice& slice, const Object& obj) { |
170 | return slice.Equals(String::Cast(obj)); |
171 | } |
172 | static bool IsMatch(const ConcatString& concat, const Object& obj) { |
173 | return concat.Equals(String::Cast(obj)); |
174 | } |
175 | static uword Hash(const Object& key) { return String::Cast(key).Hash(); } |
176 | template <typename CharType> |
177 | static uword Hash(const CharArray<CharType>& array) { |
178 | return array.Hash(); |
179 | } |
180 | static uword Hash(const StringSlice& slice) { return slice.Hash(); } |
181 | static uword Hash(const ConcatString& concat) { return concat.Hash(); } |
182 | template <typename CharType> |
183 | static ObjectPtr NewKey(const CharArray<CharType>& array) { |
184 | return array.ToSymbol(); |
185 | } |
186 | static ObjectPtr NewKey(const StringSlice& slice) { return slice.ToSymbol(); } |
187 | static ObjectPtr NewKey(const ConcatString& concat) { |
188 | return concat.ToSymbol(); |
189 | } |
190 | }; |
191 | typedef UnorderedHashSet<SymbolTraits> SymbolTable; |
192 | |
193 | const char* Symbols::Name(SymbolId symbol) { |
194 | ASSERT((symbol > kIllegal) && (symbol < kNullCharId)); |
195 | return names[symbol]; |
196 | } |
197 | |
198 | const String& Symbols::Token(Token::Kind token) { |
199 | const int tok_index = token; |
200 | ASSERT((0 <= tok_index) && (tok_index < Token::kNumTokens)); |
201 | // First keyword symbol is in symbol_handles_[kTokenTableStart + 1]. |
202 | const intptr_t token_id = Symbols::kTokenTableStart + 1 + tok_index; |
203 | ASSERT(symbol_handles_[token_id] != NULL); |
204 | return *symbol_handles_[token_id]; |
205 | } |
206 | |
207 | void Symbols::Init(Isolate* vm_isolate) { |
208 | // Should only be run by the vm isolate. |
209 | ASSERT(Isolate::Current() == Dart::vm_isolate()); |
210 | ASSERT(vm_isolate == Dart::vm_isolate()); |
211 | Zone* zone = Thread::Current()->zone(); |
212 | |
213 | // Create and setup a symbol table in the vm isolate. |
214 | SetupSymbolTable(vm_isolate); |
215 | |
216 | // Create all predefined symbols. |
217 | ASSERT((sizeof(names) / sizeof(const char*)) == Symbols::kNullCharId); |
218 | |
219 | SymbolTable table(zone, vm_isolate->object_store()->symbol_table()); |
220 | |
221 | // First set up all the predefined string symbols. |
222 | // Create symbols for language keywords. Some keywords are equal to |
223 | // symbols we already created, so use New() instead of Add() to ensure |
224 | // that the symbols are canonicalized. |
225 | for (intptr_t i = 1; i < Symbols::kNullCharId; i++) { |
226 | String* str = String::ReadOnlyHandle(); |
227 | *str = OneByteString::New(names[i], Heap::kOld); |
228 | str->Hash(); |
229 | *str ^= table.InsertOrGet(*str); |
230 | str->SetCanonical(); // Make canonical once entered. |
231 | symbol_handles_[i] = str; |
232 | } |
233 | |
234 | // Add Latin1 characters as Symbols, so that Symbols::FromCharCode is fast. |
235 | for (intptr_t c = 0; c < kNumberOfOneCharCodeSymbols; c++) { |
236 | intptr_t idx = (kNullCharId + c); |
237 | ASSERT(idx < kMaxPredefinedId); |
238 | ASSERT(Utf::IsLatin1(c)); |
239 | uint8_t ch = static_cast<uint8_t>(c); |
240 | String* str = String::ReadOnlyHandle(); |
241 | *str = OneByteString::New(&ch, 1, Heap::kOld); |
242 | str->Hash(); |
243 | *str ^= table.InsertOrGet(*str); |
244 | ASSERT(predefined_[c] == nullptr); |
245 | str->SetCanonical(); // Make canonical once entered. |
246 | predefined_[c] = str->raw(); |
247 | symbol_handles_[idx] = str; |
248 | } |
249 | |
250 | vm_isolate->object_store()->set_symbol_table(table.Release()); |
251 | } |
252 | |
253 | void Symbols::InitFromSnapshot(Isolate* vm_isolate) { |
254 | // Should only be run by the vm isolate. |
255 | ASSERT(Isolate::Current() == Dart::vm_isolate()); |
256 | ASSERT(vm_isolate == Dart::vm_isolate()); |
257 | Zone* zone = Thread::Current()->zone(); |
258 | |
259 | SymbolTable table(zone, vm_isolate->object_store()->symbol_table()); |
260 | |
261 | // Lookup all the predefined string symbols and language keyword symbols |
262 | // and cache them in the read only handles for fast access. |
263 | for (intptr_t i = 1; i < Symbols::kNullCharId; i++) { |
264 | String* str = String::ReadOnlyHandle(); |
265 | const unsigned char* name = |
266 | reinterpret_cast<const unsigned char*>(names[i]); |
267 | *str ^= table.GetOrNull(Latin1Array(name, strlen(names[i]))); |
268 | ASSERT(!str->IsNull()); |
269 | ASSERT(str->HasHash()); |
270 | ASSERT(str->IsCanonical()); |
271 | symbol_handles_[i] = str; |
272 | } |
273 | |
274 | // Lookup Latin1 character Symbols and cache them in read only handles, |
275 | // so that Symbols::FromCharCode is fast. |
276 | for (intptr_t c = 0; c < kNumberOfOneCharCodeSymbols; c++) { |
277 | intptr_t idx = (kNullCharId + c); |
278 | ASSERT(idx < kMaxPredefinedId); |
279 | ASSERT(Utf::IsLatin1(c)); |
280 | uint8_t ch = static_cast<uint8_t>(c); |
281 | String* str = String::ReadOnlyHandle(); |
282 | *str ^= table.GetOrNull(Latin1Array(&ch, 1)); |
283 | ASSERT(!str->IsNull()); |
284 | ASSERT(str->HasHash()); |
285 | ASSERT(str->IsCanonical()); |
286 | predefined_[c] = str->raw(); |
287 | symbol_handles_[idx] = str; |
288 | } |
289 | |
290 | vm_isolate->object_store()->set_symbol_table(table.Release()); |
291 | } |
292 | |
293 | void Symbols::SetupSymbolTable(Isolate* isolate) { |
294 | ASSERT(isolate != NULL); |
295 | |
296 | // Setup the symbol table used within the String class. |
297 | const intptr_t initial_size = (isolate == Dart::vm_isolate()) |
298 | ? kInitialVMIsolateSymtabSize |
299 | : kInitialSymtabSize; |
300 | Array& array = |
301 | Array::Handle(HashTables::New<SymbolTable>(initial_size, Heap::kOld)); |
302 | isolate->object_store()->set_symbol_table(array); |
303 | } |
304 | |
305 | void Symbols::Compact() { |
306 | Thread* thread = Thread::Current(); |
307 | ASSERT(thread->isolate() != Dart::vm_isolate()); |
308 | HANDLESCOPE(thread); |
309 | Zone* zone = thread->zone(); |
310 | ObjectStore* object_store = thread->isolate()->object_store(); |
311 | |
312 | // 1. Drop the tables and do a full garbage collection. |
313 | object_store->set_symbol_table(Object::empty_array()); |
314 | object_store->set_canonical_types(Object::empty_array()); |
315 | object_store->set_canonical_type_parameters(Object::empty_array()); |
316 | object_store->set_canonical_type_arguments(Object::empty_array()); |
317 | thread->heap()->CollectAllGarbage(); |
318 | |
319 | // 2. Walk the heap to find surviving canonical objects. |
320 | GrowableArray<String*> symbols; |
321 | GrowableArray<class Type*> types; |
322 | GrowableArray<class TypeParameter*> type_params; |
323 | GrowableArray<class TypeArguments*> type_args; |
324 | class SymbolCollector : public ObjectVisitor { |
325 | public: |
326 | SymbolCollector(Thread* thread, |
327 | GrowableArray<String*>* symbols, |
328 | GrowableArray<class Type*>* types, |
329 | GrowableArray<class TypeParameter*>* type_params, |
330 | GrowableArray<class TypeArguments*>* type_args) |
331 | : symbols_(symbols), |
332 | types_(types), |
333 | type_params_(type_params), |
334 | type_args_(type_args), |
335 | zone_(thread->zone()) {} |
336 | |
337 | void VisitObject(ObjectPtr obj) { |
338 | if (obj->ptr()->IsCanonical()) { |
339 | if (obj->IsStringInstance()) { |
340 | symbols_->Add(&String::Handle(zone_, String::RawCast(obj))); |
341 | } else if (obj->IsType()) { |
342 | types_->Add(&Type::Handle(zone_, Type::RawCast(obj))); |
343 | } else if (obj->IsTypeParameter()) { |
344 | type_params_->Add( |
345 | &TypeParameter::Handle(zone_, TypeParameter::RawCast(obj))); |
346 | } else if (obj->IsTypeArguments()) { |
347 | type_args_->Add( |
348 | &TypeArguments::Handle(zone_, TypeArguments::RawCast(obj))); |
349 | } |
350 | } |
351 | } |
352 | |
353 | private: |
354 | GrowableArray<String*>* symbols_; |
355 | GrowableArray<class Type*>* types_; |
356 | GrowableArray<class TypeParameter*>* type_params_; |
357 | GrowableArray<class TypeArguments*>* type_args_; |
358 | Zone* zone_; |
359 | }; |
360 | |
361 | { |
362 | HeapIterationScope iteration(thread); |
363 | SymbolCollector visitor(thread, &symbols, &types, &type_params, &type_args); |
364 | iteration.IterateObjects(&visitor); |
365 | } |
366 | |
367 | // 3. Build new tables from the surviving canonical objects. |
368 | { |
369 | Array& array = Array::Handle( |
370 | zone, |
371 | HashTables::New<SymbolTable>(symbols.length() * 4 / 3, Heap::kOld)); |
372 | SymbolTable table(zone, array.raw()); |
373 | for (intptr_t i = 0; i < symbols.length(); i++) { |
374 | String& symbol = *symbols[i]; |
375 | ASSERT(symbol.IsString()); |
376 | ASSERT(symbol.IsCanonical()); |
377 | bool present = table.Insert(symbol); |
378 | ASSERT(!present); |
379 | } |
380 | object_store->set_symbol_table(table.Release()); |
381 | } |
382 | |
383 | { |
384 | Array& array = Array::Handle(zone, HashTables::New<CanonicalTypeSet>( |
385 | types.length() * 4 / 3, Heap::kOld)); |
386 | CanonicalTypeSet table(zone, array.raw()); |
387 | for (intptr_t i = 0; i < types.length(); i++) { |
388 | class Type& type = *types[i]; |
389 | ASSERT(type.IsType()); |
390 | ASSERT(type.IsCanonical()); |
391 | bool present = table.Insert(type); |
392 | // Two recursive types with different topology (and hashes) may be equal. |
393 | ASSERT(!present || type.IsRecursive()); |
394 | } |
395 | object_store->set_canonical_types(table.Release()); |
396 | } |
397 | |
398 | { |
399 | Array& array = |
400 | Array::Handle(zone, HashTables::New<CanonicalTypeParameterSet>( |
401 | type_params.length() * 4 / 3, Heap::kOld)); |
402 | CanonicalTypeParameterSet table(zone, array.raw()); |
403 | for (intptr_t i = 0; i < type_params.length(); i++) { |
404 | class TypeParameter& type_param = *type_params[i]; |
405 | ASSERT(type_param.IsTypeParameter()); |
406 | ASSERT(type_param.IsCanonical()); |
407 | if (type_param.IsDeclaration()) continue; |
408 | bool present = table.Insert(type_param); |
409 | ASSERT(!present); |
410 | } |
411 | object_store->set_canonical_type_parameters(table.Release()); |
412 | } |
413 | |
414 | { |
415 | Array& array = |
416 | Array::Handle(zone, HashTables::New<CanonicalTypeArgumentsSet>( |
417 | type_args.length() * 4 / 3, Heap::kOld)); |
418 | CanonicalTypeArgumentsSet table(zone, array.raw()); |
419 | for (intptr_t i = 0; i < type_args.length(); i++) { |
420 | class TypeArguments& type_arg = *type_args[i]; |
421 | ASSERT(type_arg.IsTypeArguments()); |
422 | ASSERT(type_arg.IsCanonical()); |
423 | bool present = table.Insert(type_arg); |
424 | // Two recursive types with different topology (and hashes) may be equal. |
425 | ASSERT(!present || type_arg.IsRecursive()); |
426 | } |
427 | object_store->set_canonical_type_arguments(table.Release()); |
428 | } |
429 | } |
430 | |
431 | void Symbols::GetStats(Isolate* isolate, intptr_t* size, intptr_t* capacity) { |
432 | ASSERT(isolate != NULL); |
433 | SymbolTable table(isolate->object_store()->symbol_table()); |
434 | *size = table.NumOccupied(); |
435 | *capacity = table.NumEntries(); |
436 | table.Release(); |
437 | } |
438 | |
439 | StringPtr Symbols::New(Thread* thread, const char* cstr, intptr_t len) { |
440 | ASSERT((cstr != NULL) && (len >= 0)); |
441 | const uint8_t* utf8_array = reinterpret_cast<const uint8_t*>(cstr); |
442 | return Symbols::FromUTF8(thread, utf8_array, len); |
443 | } |
444 | |
445 | StringPtr Symbols::FromUTF8(Thread* thread, |
446 | const uint8_t* utf8_array, |
447 | intptr_t array_len) { |
448 | if (array_len == 0 || utf8_array == NULL) { |
449 | return FromLatin1(thread, reinterpret_cast<uint8_t*>(NULL), 0); |
450 | } |
451 | Utf8::Type type; |
452 | intptr_t len = Utf8::CodeUnitCount(utf8_array, array_len, &type); |
453 | ASSERT(len != 0); |
454 | Zone* zone = thread->zone(); |
455 | if (type == Utf8::kLatin1) { |
456 | uint8_t* characters = zone->Alloc<uint8_t>(len); |
457 | if (!Utf8::DecodeToLatin1(utf8_array, array_len, characters, len)) { |
458 | Utf8::ReportInvalidByte(utf8_array, array_len, len); |
459 | return String::null(); |
460 | } |
461 | return FromLatin1(thread, characters, len); |
462 | } |
463 | ASSERT((type == Utf8::kBMP) || (type == Utf8::kSupplementary)); |
464 | uint16_t* characters = zone->Alloc<uint16_t>(len); |
465 | if (!Utf8::DecodeToUTF16(utf8_array, array_len, characters, len)) { |
466 | Utf8::ReportInvalidByte(utf8_array, array_len, len); |
467 | return String::null(); |
468 | } |
469 | return FromUTF16(thread, characters, len); |
470 | } |
471 | |
472 | StringPtr Symbols::FromLatin1(Thread* thread, |
473 | const uint8_t* latin1_array, |
474 | intptr_t len) { |
475 | return NewSymbol(thread, Latin1Array(latin1_array, len)); |
476 | } |
477 | |
478 | StringPtr Symbols::FromUTF16(Thread* thread, |
479 | const uint16_t* utf16_array, |
480 | intptr_t len) { |
481 | return NewSymbol(thread, UTF16Array(utf16_array, len)); |
482 | } |
483 | |
484 | StringPtr Symbols::FromUTF32(Thread* thread, |
485 | const int32_t* utf32_array, |
486 | intptr_t len) { |
487 | return NewSymbol(thread, UTF32Array(utf32_array, len)); |
488 | } |
489 | |
490 | StringPtr Symbols::FromConcat(Thread* thread, |
491 | const String& str1, |
492 | const String& str2) { |
493 | if (str1.Length() == 0) { |
494 | return New(thread, str2); |
495 | } else if (str2.Length() == 0) { |
496 | return New(thread, str1); |
497 | } else { |
498 | return NewSymbol(thread, ConcatString(str1, str2)); |
499 | } |
500 | } |
501 | |
502 | StringPtr Symbols::FromGet(Thread* thread, const String& str) { |
503 | return FromConcat(thread, GetterPrefix(), str); |
504 | } |
505 | |
506 | StringPtr Symbols::FromSet(Thread* thread, const String& str) { |
507 | return FromConcat(thread, SetterPrefix(), str); |
508 | } |
509 | |
510 | StringPtr Symbols::FromDot(Thread* thread, const String& str) { |
511 | return FromConcat(thread, str, Dot()); |
512 | } |
513 | |
514 | // TODO(srdjan): If this becomes performance critical code, consider looking |
515 | // up symbol from hash of pieces instead of concatenating them first into |
516 | // a string. |
517 | StringPtr Symbols::FromConcatAll( |
518 | Thread* thread, |
519 | const GrowableHandlePtrArray<const String>& strs) { |
520 | const intptr_t strs_length = strs.length(); |
521 | GrowableArray<intptr_t> lengths(strs_length); |
522 | |
523 | intptr_t len_sum = 0; |
524 | const intptr_t kOneByteChar = 1; |
525 | intptr_t char_size = kOneByteChar; |
526 | |
527 | for (intptr_t i = 0; i < strs_length; i++) { |
528 | const String& str = strs[i]; |
529 | const intptr_t str_len = str.Length(); |
530 | if ((String::kMaxElements - len_sum) < str_len) { |
531 | Exceptions::ThrowOOM(); |
532 | UNREACHABLE(); |
533 | } |
534 | len_sum += str_len; |
535 | lengths.Add(str_len); |
536 | char_size = Utils::Maximum(char_size, str.CharSize()); |
537 | } |
538 | const bool is_one_byte_string = char_size == kOneByteChar; |
539 | |
540 | Zone* zone = thread->zone(); |
541 | if (is_one_byte_string) { |
542 | uint8_t* buffer = zone->Alloc<uint8_t>(len_sum); |
543 | const uint8_t* const orig_buffer = buffer; |
544 | for (intptr_t i = 0; i < strs_length; i++) { |
545 | NoSafepointScope no_safepoint; |
546 | intptr_t str_len = lengths[i]; |
547 | if (str_len > 0) { |
548 | const String& str = strs[i]; |
549 | ASSERT(str.IsOneByteString() || str.IsExternalOneByteString()); |
550 | const uint8_t* src_p = str.IsOneByteString() |
551 | ? OneByteString::DataStart(str) |
552 | : ExternalOneByteString::DataStart(str); |
553 | memmove(buffer, src_p, str_len); |
554 | buffer += str_len; |
555 | } |
556 | } |
557 | ASSERT(len_sum == buffer - orig_buffer); |
558 | return Symbols::FromLatin1(thread, orig_buffer, len_sum); |
559 | } else { |
560 | uint16_t* buffer = zone->Alloc<uint16_t>(len_sum); |
561 | const uint16_t* const orig_buffer = buffer; |
562 | for (intptr_t i = 0; i < strs_length; i++) { |
563 | NoSafepointScope no_safepoint; |
564 | intptr_t str_len = lengths[i]; |
565 | if (str_len > 0) { |
566 | const String& str = strs[i]; |
567 | if (str.IsTwoByteString()) { |
568 | memmove(buffer, TwoByteString::DataStart(str), str_len * 2); |
569 | } else if (str.IsExternalTwoByteString()) { |
570 | memmove(buffer, ExternalTwoByteString::DataStart(str), str_len * 2); |
571 | } else { |
572 | // One-byte to two-byte string copy. |
573 | ASSERT(str.IsOneByteString() || str.IsExternalOneByteString()); |
574 | const uint8_t* src_p = str.IsOneByteString() |
575 | ? OneByteString::DataStart(str) |
576 | : ExternalOneByteString::DataStart(str); |
577 | for (int n = 0; n < str_len; n++) { |
578 | buffer[n] = src_p[n]; |
579 | } |
580 | } |
581 | buffer += str_len; |
582 | } |
583 | } |
584 | ASSERT(len_sum == buffer - orig_buffer); |
585 | return Symbols::FromUTF16(thread, orig_buffer, len_sum); |
586 | } |
587 | } |
588 | |
589 | // StringType can be StringSlice, ConcatString, or {Latin1,UTF16,UTF32}Array. |
590 | template <typename StringType> |
591 | StringPtr Symbols::NewSymbol(Thread* thread, const StringType& str) { |
592 | REUSABLE_OBJECT_HANDLESCOPE(thread); |
593 | REUSABLE_SMI_HANDLESCOPE(thread); |
594 | REUSABLE_ARRAY_HANDLESCOPE(thread); |
595 | String& symbol = String::Handle(thread->zone()); |
596 | dart::Object& key = thread->ObjectHandle(); |
597 | Smi& value = thread->SmiHandle(); |
598 | Array& data = thread->ArrayHandle(); |
599 | { |
600 | Isolate* vm_isolate = Dart::vm_isolate(); |
601 | data = vm_isolate->object_store()->symbol_table(); |
602 | SymbolTable table(&key, &value, &data); |
603 | symbol ^= table.GetOrNull(str); |
604 | table.Release(); |
605 | } |
606 | if (symbol.IsNull()) { |
607 | IsolateGroup* group = thread->isolate_group(); |
608 | Isolate* isolate = thread->isolate(); |
609 | // In JIT object_store lives on isolate, not on isolate group. |
610 | ObjectStore* object_store = group->object_store() == nullptr |
611 | ? isolate->object_store() |
612 | : group->object_store(); |
613 | if (thread->IsAtSafepoint()) { |
614 | // There are two cases where we can cause symbol allocation while holding |
615 | // a safepoint: |
616 | // - FLAG_enable_isolate_groups in AOT due to the usage of |
617 | // `RunWithStoppedMutators` in SwitchableCall runtime entry. |
618 | // - non-PRODUCT mode where the vm-service uses a HeapIterationScope |
619 | // while building instances |
620 | // Ideally we should get rid of both cases to avoid this unsafe usage of |
621 | // the symbol table (we are assuming here that no other thread holds the |
622 | // symbols_lock). |
623 | // TODO(https://dartbug.com/41943): Get rid of the symbol table accesses |
624 | // within safepoint operation scope. |
625 | RELEASE_ASSERT(group->safepoint_handler()->IsOwnedByTheThread(thread)); |
626 | RELEASE_ASSERT(FLAG_enable_isolate_groups || !USING_PRODUCT); |
627 | |
628 | // Uncommon case: We are at a safepoint, all mutators are stopped and we |
629 | // have therefore exclusive access to the symbol table. |
630 | data = object_store->symbol_table(); |
631 | SymbolTable table(&key, &value, &data); |
632 | symbol ^= table.InsertNewOrGet(str); |
633 | object_store->set_symbol_table(table.Release()); |
634 | } else { |
635 | // Most common case: We are not at a safepoint and the symbol is available |
636 | // in the symbol table: We require only read access. |
637 | { |
638 | SafepointReadRwLocker sl(thread, group->symbols_lock()); |
639 | data = object_store->symbol_table(); |
640 | SymbolTable table(&key, &value, &data); |
641 | symbol ^= table.GetOrNull(str); |
642 | table.Release(); |
643 | } |
644 | // Second common case: We are not at a safepoint and the symbol is not |
645 | // available in the symbol table: We require only exclusive access. |
646 | if (symbol.IsNull()) { |
647 | auto insert_or_get = [&]() { |
648 | data = object_store->symbol_table(); |
649 | SymbolTable table(&key, &value, &data); |
650 | symbol ^= table.InsertNewOrGet(str); |
651 | object_store->set_symbol_table(table.Release()); |
652 | }; |
653 | |
654 | SafepointWriteRwLocker sl(thread, group->symbols_lock()); |
655 | if (FLAG_enable_isolate_groups || !USING_PRODUCT) { |
656 | // NOTE: Strictly speaking we should use a safepoint operation scope |
657 | // here to ensure the lock-free usage inside safepoint operations (see |
658 | // above) is safe. Though this would really kill the performance. |
659 | // TODO(https://dartbug.com/41943): Get rid of the symbol table |
660 | // accesses within safepoint operation scope. |
661 | group->RunWithStoppedMutators(insert_or_get, |
662 | /*force_heap_growth=*/true); |
663 | } else { |
664 | insert_or_get(); |
665 | } |
666 | } |
667 | } |
668 | } |
669 | ASSERT(symbol.IsSymbol()); |
670 | ASSERT(symbol.HasHash()); |
671 | return symbol.raw(); |
672 | } |
673 | |
674 | template <typename StringType> |
675 | StringPtr Symbols::Lookup(Thread* thread, const StringType& str) { |
676 | REUSABLE_OBJECT_HANDLESCOPE(thread); |
677 | REUSABLE_SMI_HANDLESCOPE(thread); |
678 | REUSABLE_ARRAY_HANDLESCOPE(thread); |
679 | String& symbol = String::Handle(thread->zone()); |
680 | dart::Object& key = thread->ObjectHandle(); |
681 | Smi& value = thread->SmiHandle(); |
682 | Array& data = thread->ArrayHandle(); |
683 | { |
684 | Isolate* vm_isolate = Dart::vm_isolate(); |
685 | data = vm_isolate->object_store()->symbol_table(); |
686 | SymbolTable table(&key, &value, &data); |
687 | symbol ^= table.GetOrNull(str); |
688 | table.Release(); |
689 | } |
690 | if (symbol.IsNull()) { |
691 | IsolateGroup* group = thread->isolate_group(); |
692 | Isolate* isolate = thread->isolate(); |
693 | // In JIT object_store lives on isolate, not on isolate group. |
694 | ObjectStore* object_store = group->object_store() == nullptr |
695 | ? isolate->object_store() |
696 | : group->object_store(); |
697 | // See `Symbols::NewSymbol` for more information why we separate the two |
698 | // cases. |
699 | if (thread->IsAtSafepoint()) { |
700 | RELEASE_ASSERT(group->safepoint_handler()->IsOwnedByTheThread(thread)); |
701 | // In DEBUG mode the snapshot writer also calls this method inside a |
702 | // safepoint. |
703 | #if !defined(DEBUG) |
704 | RELEASE_ASSERT(FLAG_enable_isolate_groups || !USING_PRODUCT); |
705 | #endif |
706 | data = object_store->symbol_table(); |
707 | SymbolTable table(&key, &value, &data); |
708 | symbol ^= table.GetOrNull(str); |
709 | table.Release(); |
710 | } else { |
711 | SafepointReadRwLocker sl(thread, group->symbols_lock()); |
712 | data = object_store->symbol_table(); |
713 | SymbolTable table(&key, &value, &data); |
714 | symbol ^= table.GetOrNull(str); |
715 | table.Release(); |
716 | } |
717 | } |
718 | ASSERT(symbol.IsNull() || symbol.IsSymbol()); |
719 | ASSERT(symbol.IsNull() || symbol.HasHash()); |
720 | return symbol.raw(); |
721 | } |
722 | |
723 | StringPtr Symbols::LookupFromConcat(Thread* thread, |
724 | const String& str1, |
725 | const String& str2) { |
726 | if (str1.Length() == 0) { |
727 | return Lookup(thread, str2); |
728 | } else if (str2.Length() == 0) { |
729 | return Lookup(thread, str1); |
730 | } else { |
731 | return Lookup(thread, ConcatString(str1, str2)); |
732 | } |
733 | } |
734 | |
735 | StringPtr Symbols::LookupFromGet(Thread* thread, const String& str) { |
736 | return LookupFromConcat(thread, GetterPrefix(), str); |
737 | } |
738 | |
739 | StringPtr Symbols::LookupFromSet(Thread* thread, const String& str) { |
740 | return LookupFromConcat(thread, SetterPrefix(), str); |
741 | } |
742 | |
743 | StringPtr Symbols::LookupFromDot(Thread* thread, const String& str) { |
744 | return LookupFromConcat(thread, str, Dot()); |
745 | } |
746 | |
747 | StringPtr Symbols::New(Thread* thread, const String& str) { |
748 | if (str.IsSymbol()) { |
749 | return str.raw(); |
750 | } |
751 | return New(thread, str, 0, str.Length()); |
752 | } |
753 | |
754 | StringPtr Symbols::New(Thread* thread, |
755 | const String& str, |
756 | intptr_t begin_index, |
757 | intptr_t len) { |
758 | return NewSymbol(thread, StringSlice(str, begin_index, len)); |
759 | } |
760 | |
761 | StringPtr Symbols::NewFormatted(Thread* thread, const char* format, ...) { |
762 | va_list args; |
763 | va_start(args, format); |
764 | StringPtr result = NewFormattedV(thread, format, args); |
765 | NoSafepointScope no_safepoint; |
766 | va_end(args); |
767 | return result; |
768 | } |
769 | |
770 | StringPtr Symbols::NewFormattedV(Thread* thread, |
771 | const char* format, |
772 | va_list args) { |
773 | va_list args_copy; |
774 | va_copy(args_copy, args); |
775 | intptr_t len = Utils::VSNPrint(NULL, 0, format, args_copy); |
776 | va_end(args_copy); |
777 | |
778 | Zone* zone = Thread::Current()->zone(); |
779 | char* buffer = zone->Alloc<char>(len + 1); |
780 | Utils::VSNPrint(buffer, (len + 1), format, args); |
781 | |
782 | return Symbols::New(thread, buffer); |
783 | } |
784 | |
785 | StringPtr Symbols::FromCharCode(Thread* thread, int32_t char_code) { |
786 | if (char_code > kMaxOneCharCodeSymbol) { |
787 | return FromUTF32(thread, &char_code, 1); |
788 | } |
789 | return predefined_[char_code]; |
790 | } |
791 | |
792 | void Symbols::DumpStats(Isolate* isolate) { |
793 | intptr_t size = -1; |
794 | intptr_t capacity = -1; |
795 | // First dump VM symbol table stats. |
796 | GetStats(Dart::vm_isolate(), &size, &capacity); |
797 | OS::PrintErr("VM Isolate: Number of symbols : %" Pd "\n" , size); |
798 | OS::PrintErr("VM Isolate: Symbol table capacity : %" Pd "\n" , capacity); |
799 | // Now dump regular isolate symbol table stats. |
800 | GetStats(isolate, &size, &capacity); |
801 | OS::PrintErr("Isolate: Number of symbols : %" Pd "\n" , size); |
802 | OS::PrintErr("Isolate: Symbol table capacity : %" Pd "\n" , capacity); |
803 | // TODO(koda): Consider recording growth and collision stats in HashTable, |
804 | // in DEBUG mode. |
805 | } |
806 | |
807 | void Symbols::DumpTable(Isolate* isolate) { |
808 | OS::PrintErr("symbols:\n" ); |
809 | SymbolTable table(isolate->object_store()->symbol_table()); |
810 | table.Dump(); |
811 | table.Release(); |
812 | } |
813 | |
814 | intptr_t Symbols::LookupPredefinedSymbol(ObjectPtr obj) { |
815 | for (intptr_t i = 1; i < Symbols::kMaxPredefinedId; i++) { |
816 | if (symbol_handles_[i]->raw() == obj) { |
817 | return (i + kMaxPredefinedObjectIds); |
818 | } |
819 | } |
820 | return kInvalidIndex; |
821 | } |
822 | |
823 | ObjectPtr Symbols::GetPredefinedSymbol(intptr_t object_id) { |
824 | ASSERT(IsPredefinedSymbolId(object_id)); |
825 | intptr_t i = (object_id - kMaxPredefinedObjectIds); |
826 | if ((i > kIllegal) && (i < Symbols::kMaxPredefinedId)) { |
827 | return symbol_handles_[i]->raw(); |
828 | } |
829 | return Object::null(); |
830 | } |
831 | |
832 | } // namespace dart |
833 | |