1 | // Copyright (c) 2016, 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/isolate_reload.h" |
6 | |
7 | #include <memory> |
8 | |
9 | #include "vm/bit_vector.h" |
10 | #include "vm/compiler/jit/compiler.h" |
11 | #include "vm/dart_api_impl.h" |
12 | #if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME) |
13 | #include "vm/hash.h" |
14 | #endif |
15 | #include "vm/hash_table.h" |
16 | #include "vm/heap/become.h" |
17 | #include "vm/heap/safepoint.h" |
18 | #include "vm/isolate.h" |
19 | #include "vm/kernel_isolate.h" |
20 | #include "vm/kernel_loader.h" |
21 | #include "vm/log.h" |
22 | #include "vm/object.h" |
23 | #include "vm/object_store.h" |
24 | #include "vm/parser.h" |
25 | #include "vm/runtime_entry.h" |
26 | #include "vm/service_event.h" |
27 | #include "vm/stack_frame.h" |
28 | #include "vm/thread.h" |
29 | #include "vm/timeline.h" |
30 | #include "vm/type_testing_stubs.h" |
31 | #include "vm/visitor.h" |
32 | |
33 | namespace dart { |
34 | |
35 | DEFINE_FLAG(int, reload_every, 0, "Reload every N stack overflow checks." ); |
36 | DEFINE_FLAG(bool, trace_reload, false, "Trace isolate reloading" ); |
37 | |
38 | #if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME) |
39 | DEFINE_FLAG(bool, |
40 | trace_reload_verbose, |
41 | false, |
42 | "trace isolate reloading verbose" ); |
43 | DEFINE_FLAG(bool, identity_reload, false, "Enable checks for identity reload." ); |
44 | DEFINE_FLAG(bool, reload_every_optimized, true, "Only from optimized code." ); |
45 | DEFINE_FLAG(bool, |
46 | reload_every_back_off, |
47 | false, |
48 | "Double the --reload-every value after each reload." ); |
49 | DEFINE_FLAG(bool, |
50 | reload_force_rollback, |
51 | false, |
52 | "Force all reloads to fail and rollback." ); |
53 | DEFINE_FLAG(bool, |
54 | check_reloaded, |
55 | false, |
56 | "Assert that an isolate has reloaded at least once." ) |
57 | DEFINE_FLAG(bool, gc_during_reload, false, "Cause explicit GC during reload." ); |
58 | |
59 | DECLARE_FLAG(bool, trace_deoptimization); |
60 | |
61 | #define I (isolate()) |
62 | #define Z zone_ |
63 | |
64 | #define TIMELINE_SCOPE(name) \ |
65 | TimelineBeginEndScope tbes##name(Thread::Current(), \ |
66 | Timeline::GetIsolateStream(), #name) |
67 | |
68 | // The ObjectLocator is used for collecting instances that |
69 | // needs to be morphed. |
70 | class ObjectLocator : public ObjectVisitor { |
71 | public: |
72 | explicit ObjectLocator(IsolateGroupReloadContext* context) |
73 | : context_(context), count_(0) {} |
74 | |
75 | void VisitObject(ObjectPtr obj) { |
76 | InstanceMorpher* morpher = |
77 | context_->instance_morpher_by_cid_.LookupValue(obj->GetClassId()); |
78 | if (morpher != NULL) { |
79 | morpher->AddObject(obj); |
80 | count_++; |
81 | } |
82 | } |
83 | |
84 | // Return the number of located objects for morphing. |
85 | intptr_t count() { return count_; } |
86 | |
87 | private: |
88 | IsolateGroupReloadContext* context_; |
89 | intptr_t count_; |
90 | }; |
91 | |
92 | static bool HasNoTasks(Heap* heap) { |
93 | MonitorLocker ml(heap->old_space()->tasks_lock()); |
94 | return heap->old_space()->tasks() == 0; |
95 | } |
96 | |
97 | // TODO(dartbug.com/36097): Once classes are split up into a read-only |
98 | // descriptor which can be shared across isolates, we can make this function |
99 | // take descriptors instead of the isolate-specific [Class] objects. |
100 | // |
101 | // (The information we access from [from]/[to] *must* be the same across |
102 | // isolates.) |
103 | InstanceMorpher* InstanceMorpher::CreateFromClassDescriptors( |
104 | Zone* zone, |
105 | SharedClassTable* shared_class_table, |
106 | const Class& from, |
107 | const Class& to) { |
108 | auto mapping = new (zone) ZoneGrowableArray<intptr_t>(); |
109 | auto new_fields_offsets = new (zone) ZoneGrowableArray<intptr_t>(); |
110 | |
111 | if (from.NumTypeArguments() > 0) { |
112 | // Add copying of the optional type argument field. |
113 | intptr_t from_offset = from.host_type_arguments_field_offset(); |
114 | ASSERT(from_offset != Class::kNoTypeArguments); |
115 | intptr_t to_offset = to.host_type_arguments_field_offset(); |
116 | ASSERT(to_offset != Class::kNoTypeArguments); |
117 | mapping->Add(from_offset); |
118 | mapping->Add(to_offset); |
119 | } |
120 | |
121 | // Add copying of the instance fields if matching by name. |
122 | // Note: currently the type of the fields are ignored. |
123 | const Array& from_fields = |
124 | Array::Handle(from.OffsetToFieldMap(true /* original classes */)); |
125 | const Array& to_fields = Array::Handle(to.OffsetToFieldMap()); |
126 | Field& from_field = Field::Handle(); |
127 | Field& to_field = Field::Handle(); |
128 | String& from_name = String::Handle(); |
129 | String& to_name = String::Handle(); |
130 | |
131 | // Scan across all the fields in the new class definition. |
132 | for (intptr_t i = 0; i < to_fields.Length(); i++) { |
133 | if (to_fields.At(i) == Field::null()) { |
134 | continue; // Ignore non-fields. |
135 | } |
136 | |
137 | // Grab the field's name. |
138 | to_field = Field::RawCast(to_fields.At(i)); |
139 | ASSERT(to_field.is_instance()); |
140 | to_name = to_field.name(); |
141 | |
142 | // Did this field not exist in the old class definition? |
143 | bool new_field = true; |
144 | |
145 | // Find this field in the old class. |
146 | for (intptr_t j = 0; j < from_fields.Length(); j++) { |
147 | if (from_fields.At(j) == Field::null()) { |
148 | continue; // Ignore non-fields. |
149 | } |
150 | from_field = Field::RawCast(from_fields.At(j)); |
151 | ASSERT(from_field.is_instance()); |
152 | from_name = from_field.name(); |
153 | if (from_name.Equals(to_name)) { |
154 | // Success |
155 | mapping->Add(from_field.HostOffset()); |
156 | mapping->Add(to_field.HostOffset()); |
157 | // Field did exist in old class deifnition. |
158 | new_field = false; |
159 | } |
160 | } |
161 | |
162 | if (new_field) { |
163 | const Field& field = Field::Handle(to_field.raw()); |
164 | field.set_needs_load_guard(true); |
165 | field.set_is_unboxing_candidate(false); |
166 | new_fields_offsets->Add(field.HostOffset()); |
167 | } |
168 | } |
169 | |
170 | ASSERT(from.id() == to.id()); |
171 | return new (zone) InstanceMorpher(zone, to.id(), shared_class_table, mapping, |
172 | new_fields_offsets); |
173 | } |
174 | |
175 | InstanceMorpher::InstanceMorpher( |
176 | Zone* zone, |
177 | classid_t cid, |
178 | SharedClassTable* shared_class_table, |
179 | ZoneGrowableArray<intptr_t>* mapping, |
180 | ZoneGrowableArray<intptr_t>* new_fields_offsets) |
181 | : zone_(zone), |
182 | cid_(cid), |
183 | shared_class_table_(shared_class_table), |
184 | mapping_(mapping), |
185 | new_fields_offsets_(new_fields_offsets), |
186 | before_(zone, 16), |
187 | after_(zone, 16) {} |
188 | |
189 | void InstanceMorpher::AddObject(ObjectPtr object) { |
190 | ASSERT(object->GetClassId() == cid_); |
191 | const Instance& instance = Instance::Cast(Object::Handle(Z, object)); |
192 | before_.Add(&instance); |
193 | } |
194 | |
195 | InstancePtr InstanceMorpher::Morph(const Instance& instance) const { |
196 | // Code can reference constants / canonical objects either directly in the |
197 | // instruction stream (ia32) or via an object pool. |
198 | // |
199 | // We have the following invariants: |
200 | // |
201 | // a) Those canonical objects don't change state (i.e. are not mutable): |
202 | // our optimizer can e.g. execute loads of such constants at |
203 | // compile-time. |
204 | // |
205 | // => We ensure that const-classes with live constants cannot be |
206 | // reloaded to become non-const classes (see Class::CheckReload). |
207 | // |
208 | // b) Those canonical objects live in old space: e.g. on ia32 the scavenger |
209 | // does not make the RX pages writable and therefore cannot update |
210 | // pointers embedded in the instruction stream. |
211 | // |
212 | // In order to maintain these invariants we ensure to always morph canonical |
213 | // objects to old space. |
214 | const bool is_canonical = instance.IsCanonical(); |
215 | const Heap::Space space = is_canonical ? Heap::kOld : Heap::kNew; |
216 | const auto& result = Instance::Handle( |
217 | Z, Instance::NewFromCidAndSize(shared_class_table_, cid_, space)); |
218 | |
219 | // We preserve the canonical bit of the object, since this object is present |
220 | // in the class's constants. |
221 | if (is_canonical) { |
222 | result.SetCanonical(); |
223 | } |
224 | #if defined(HASH_IN_OBJECT_HEADER) |
225 | const uint32_t hash = Object::GetCachedHash(instance.raw()); |
226 | Object::SetCachedHash(result.raw(), hash); |
227 | #endif |
228 | |
229 | // Morph the context from instance to result using mapping_. |
230 | Object& value = Object::Handle(Z); |
231 | for (intptr_t i = 0; i < mapping_->length(); i += 2) { |
232 | intptr_t from_offset = mapping_->At(i); |
233 | intptr_t to_offset = mapping_->At(i + 1); |
234 | ASSERT(from_offset > 0); |
235 | ASSERT(to_offset > 0); |
236 | value = instance.RawGetFieldAtOffset(from_offset); |
237 | result.RawSetFieldAtOffset(to_offset, value); |
238 | } |
239 | |
240 | for (intptr_t i = 0; i < new_fields_offsets_->length(); i++) { |
241 | const intptr_t field_offset = new_fields_offsets_->At(i); |
242 | result.RawSetFieldAtOffset(field_offset, Object::sentinel()); |
243 | } |
244 | |
245 | // Convert the instance into a filler object. |
246 | Become::MakeDummyObject(instance); |
247 | return result.raw(); |
248 | } |
249 | |
250 | void InstanceMorpher::CreateMorphedCopies() { |
251 | for (intptr_t i = 0; i < before_.length(); i++) { |
252 | const Instance& copy = Instance::Handle(Z, Morph(*before_.At(i))); |
253 | after_.Add(©); |
254 | } |
255 | } |
256 | |
257 | void InstanceMorpher::Dump() const { |
258 | LogBlock blocker; |
259 | THR_Print("Morphing objects with cid: %d via this mapping: " , cid_); |
260 | for (int i = 0; i < mapping_->length(); i += 2) { |
261 | THR_Print(" %" Pd "->%" Pd, mapping_->At(i), mapping_->At(i + 1)); |
262 | } |
263 | THR_Print("\n" ); |
264 | } |
265 | |
266 | void InstanceMorpher::AppendTo(JSONArray* array) { |
267 | JSONObject jsobj(array); |
268 | jsobj.AddProperty("type" , "ShapeChangeMapping" ); |
269 | jsobj.AddProperty64("class-id" , cid_); |
270 | jsobj.AddProperty("instanceCount" , before_.length()); |
271 | JSONArray map(&jsobj, "fieldOffsetMappings" ); |
272 | for (int i = 0; i < mapping_->length(); i += 2) { |
273 | JSONArray pair(&map); |
274 | pair.AddValue(mapping_->At(i)); |
275 | pair.AddValue(mapping_->At(i + 1)); |
276 | } |
277 | } |
278 | |
279 | void ReasonForCancelling::Report(IsolateGroupReloadContext* context) { |
280 | const Error& error = Error::Handle(ToError()); |
281 | context->ReportError(error); |
282 | } |
283 | |
284 | ErrorPtr ReasonForCancelling::ToError() { |
285 | // By default create the error returned from ToString. |
286 | const String& message = String::Handle(ToString()); |
287 | return LanguageError::New(message); |
288 | } |
289 | |
290 | StringPtr ReasonForCancelling::ToString() { |
291 | UNREACHABLE(); |
292 | return NULL; |
293 | } |
294 | |
295 | void ReasonForCancelling::AppendTo(JSONArray* array) { |
296 | JSONObject jsobj(array); |
297 | jsobj.AddProperty("type" , "ReasonForCancelling" ); |
298 | const String& message = String::Handle(ToString()); |
299 | jsobj.AddProperty("message" , message.ToCString()); |
300 | } |
301 | |
302 | ClassReasonForCancelling::ClassReasonForCancelling(Zone* zone, |
303 | const Class& from, |
304 | const Class& to) |
305 | : ReasonForCancelling(zone), |
306 | from_(Class::ZoneHandle(zone, from.raw())), |
307 | to_(Class::ZoneHandle(zone, to.raw())) {} |
308 | |
309 | void ClassReasonForCancelling::AppendTo(JSONArray* array) { |
310 | JSONObject jsobj(array); |
311 | jsobj.AddProperty("type" , "ReasonForCancelling" ); |
312 | jsobj.AddProperty("class" , from_); |
313 | const String& message = String::Handle(ToString()); |
314 | jsobj.AddProperty("message" , message.ToCString()); |
315 | } |
316 | |
317 | ErrorPtr IsolateGroupReloadContext::error() const { |
318 | ASSERT(!reasons_to_cancel_reload_.is_empty()); |
319 | // Report the first error to the surroundings. |
320 | return reasons_to_cancel_reload_.At(0)->ToError(); |
321 | } |
322 | |
323 | class ScriptUrlSetTraits { |
324 | public: |
325 | static bool ReportStats() { return false; } |
326 | static const char* Name() { return "ScriptUrlSetTraits" ; } |
327 | |
328 | static bool IsMatch(const Object& a, const Object& b) { |
329 | if (!a.IsString() || !b.IsString()) { |
330 | return false; |
331 | } |
332 | |
333 | return String::Cast(a).Equals(String::Cast(b)); |
334 | } |
335 | |
336 | static uword Hash(const Object& obj) { return String::Cast(obj).Hash(); } |
337 | }; |
338 | |
339 | class ClassMapTraits { |
340 | public: |
341 | static bool ReportStats() { return false; } |
342 | static const char* Name() { return "ClassMapTraits" ; } |
343 | |
344 | static bool IsMatch(const Object& a, const Object& b) { |
345 | if (!a.IsClass() || !b.IsClass()) { |
346 | return false; |
347 | } |
348 | return IsolateReloadContext::IsSameClass(Class::Cast(a), Class::Cast(b)); |
349 | } |
350 | |
351 | static uword Hash(const Object& obj) { |
352 | uword class_name_hash = String::HashRawSymbol(Class::Cast(obj).Name()); |
353 | LibraryPtr raw_library = Class::Cast(obj).library(); |
354 | if (raw_library == Library::null()) { |
355 | return class_name_hash; |
356 | } |
357 | return FinalizeHash( |
358 | CombineHashes(class_name_hash, |
359 | String::Hash(Library::Handle(raw_library).private_key())), |
360 | /* hashbits= */ 30); |
361 | } |
362 | }; |
363 | |
364 | class LibraryMapTraits { |
365 | public: |
366 | static bool ReportStats() { return false; } |
367 | static const char* Name() { return "LibraryMapTraits" ; } |
368 | |
369 | static bool IsMatch(const Object& a, const Object& b) { |
370 | if (!a.IsLibrary() || !b.IsLibrary()) { |
371 | return false; |
372 | } |
373 | return IsolateReloadContext::IsSameLibrary(Library::Cast(a), |
374 | Library::Cast(b)); |
375 | } |
376 | |
377 | static uword Hash(const Object& obj) { return Library::Cast(obj).UrlHash(); } |
378 | }; |
379 | |
380 | class BecomeMapTraits { |
381 | public: |
382 | static bool ReportStats() { return false; } |
383 | static const char* Name() { return "BecomeMapTraits" ; } |
384 | |
385 | static bool IsMatch(const Object& a, const Object& b) { |
386 | return a.raw() == b.raw(); |
387 | } |
388 | |
389 | static uword Hash(const Object& obj) { |
390 | if (obj.IsLibrary()) { |
391 | return Library::Cast(obj).UrlHash(); |
392 | } else if (obj.IsClass()) { |
393 | return String::HashRawSymbol(Class::Cast(obj).Name()); |
394 | } else if (obj.IsField()) { |
395 | return String::HashRawSymbol(Field::Cast(obj).name()); |
396 | } else if (obj.IsClosure()) { |
397 | return String::HashRawSymbol( |
398 | Function::Handle(Closure::Cast(obj).function()).name()); |
399 | } else { |
400 | FATAL1("Unexpected type in become: %s\n" , obj.ToCString()); |
401 | } |
402 | return 0; |
403 | } |
404 | }; |
405 | |
406 | bool IsolateReloadContext::IsSameClass(const Class& a, const Class& b) { |
407 | // TODO(turnidge): We need to look at generic type arguments for |
408 | // synthetic mixin classes. Their names are not necessarily unique |
409 | // currently. |
410 | const String& a_name = String::Handle(a.Name()); |
411 | const String& b_name = String::Handle(b.Name()); |
412 | |
413 | if (!a_name.Equals(b_name)) { |
414 | return false; |
415 | } |
416 | |
417 | const Library& a_lib = Library::Handle(a.library()); |
418 | const Library& b_lib = Library::Handle(b.library()); |
419 | |
420 | if (a_lib.IsNull() || b_lib.IsNull()) { |
421 | return a_lib.raw() == b_lib.raw(); |
422 | } |
423 | return (a_lib.private_key() == b_lib.private_key()); |
424 | } |
425 | |
426 | bool IsolateReloadContext::IsSameLibrary(const Library& a_lib, |
427 | const Library& b_lib) { |
428 | const String& a_lib_url = |
429 | String::Handle(a_lib.IsNull() ? String::null() : a_lib.url()); |
430 | const String& b_lib_url = |
431 | String::Handle(b_lib.IsNull() ? String::null() : b_lib.url()); |
432 | return a_lib_url.Equals(b_lib_url); |
433 | } |
434 | |
435 | IsolateGroupReloadContext::IsolateGroupReloadContext( |
436 | IsolateGroup* isolate_group, |
437 | SharedClassTable* shared_class_table, |
438 | JSONStream* js) |
439 | : zone_(Thread::Current()->zone()), |
440 | isolate_group_(isolate_group), |
441 | shared_class_table_(shared_class_table), |
442 | start_time_micros_(OS::GetCurrentMonotonicMicros()), |
443 | reload_timestamp_(OS::GetCurrentTimeMillis()), |
444 | js_(js), |
445 | saved_size_table_(nullptr), |
446 | instance_morphers_(zone_, 0), |
447 | reasons_to_cancel_reload_(zone_, 0), |
448 | instance_morpher_by_cid_(zone_), |
449 | root_lib_url_(String::Handle(Z, String::null())), |
450 | root_url_prefix_(String::null()), |
451 | old_root_url_prefix_(String::null()) {} |
452 | IsolateGroupReloadContext::~IsolateGroupReloadContext() {} |
453 | |
454 | IsolateReloadContext::IsolateReloadContext( |
455 | std::shared_ptr<IsolateGroupReloadContext> group_reload_context, |
456 | Isolate* isolate) |
457 | : zone_(Thread::Current()->zone()), |
458 | group_reload_context_(group_reload_context), |
459 | isolate_(isolate), |
460 | saved_class_table_(nullptr), |
461 | saved_tlc_class_table_(nullptr), |
462 | old_classes_set_storage_(Array::null()), |
463 | class_map_storage_(Array::null()), |
464 | removed_class_set_storage_(Array::null()), |
465 | old_libraries_set_storage_(Array::null()), |
466 | library_map_storage_(Array::null()), |
467 | become_map_storage_(Array::null()), |
468 | become_enum_mappings_(GrowableObjectArray::null()), |
469 | saved_root_library_(Library::null()), |
470 | saved_libraries_(GrowableObjectArray::null()) { |
471 | // NOTE: DO NOT ALLOCATE ANY RAW OBJECTS HERE. The IsolateReloadContext is not |
472 | // associated with the isolate yet and if a GC is triggered here the raw |
473 | // objects will not be properly accounted for. |
474 | ASSERT(zone_ != NULL); |
475 | } |
476 | |
477 | IsolateReloadContext::~IsolateReloadContext() { |
478 | ASSERT(zone_ == Thread::Current()->zone()); |
479 | ASSERT(saved_class_table_.load(std::memory_order_relaxed) == nullptr); |
480 | ASSERT(saved_tlc_class_table_.load(std::memory_order_relaxed) == nullptr); |
481 | } |
482 | |
483 | void IsolateGroupReloadContext::ReportError(const Error& error) { |
484 | // TODO(dartbug.com/36097): We need to change the "reloadSources" service-api |
485 | // call to accept an isolate group instead of an isolate. |
486 | Isolate* isolate = Isolate::Current(); |
487 | if (Isolate::IsVMInternalIsolate(isolate)) { |
488 | return; |
489 | } |
490 | TIR_Print("ISO-RELOAD: Error: %s\n" , error.ToErrorCString()); |
491 | ServiceEvent service_event(isolate, ServiceEvent::kIsolateReload); |
492 | service_event.set_reload_error(&error); |
493 | Service::HandleEvent(&service_event); |
494 | } |
495 | |
496 | void IsolateGroupReloadContext::ReportSuccess() { |
497 | // TODO(dartbug.com/36097): We need to change the "reloadSources" service-api |
498 | // call to accept an isolate group instead of an isolate. |
499 | Isolate* isolate = Isolate::Current(); |
500 | if (Isolate::IsVMInternalIsolate(isolate)) { |
501 | return; |
502 | } |
503 | ServiceEvent service_event(isolate, ServiceEvent::kIsolateReload); |
504 | Service::HandleEvent(&service_event); |
505 | } |
506 | |
507 | class Aborted : public ReasonForCancelling { |
508 | public: |
509 | Aborted(Zone* zone, const Error& error) |
510 | : ReasonForCancelling(zone), |
511 | error_(Error::ZoneHandle(zone, error.raw())) {} |
512 | |
513 | private: |
514 | const Error& error_; |
515 | |
516 | ErrorPtr ToError() { return error_.raw(); } |
517 | StringPtr ToString() { |
518 | return String::NewFormatted("%s" , error_.ToErrorCString()); |
519 | } |
520 | }; |
521 | |
522 | static intptr_t CommonSuffixLength(const char* a, const char* b) { |
523 | const intptr_t a_length = strlen(a); |
524 | const intptr_t b_length = strlen(b); |
525 | intptr_t a_cursor = a_length; |
526 | intptr_t b_cursor = b_length; |
527 | |
528 | while ((a_cursor >= 0) && (b_cursor >= 0)) { |
529 | if (a[a_cursor] != b[b_cursor]) { |
530 | break; |
531 | } |
532 | a_cursor--; |
533 | b_cursor--; |
534 | } |
535 | |
536 | ASSERT((a_length - a_cursor) == (b_length - b_cursor)); |
537 | return (a_length - a_cursor); |
538 | } |
539 | |
540 | static void AcceptCompilation(Thread* thread) { |
541 | TransitionVMToNative transition(thread); |
542 | Dart_KernelCompilationResult result = KernelIsolate::AcceptCompilation(); |
543 | if (result.status != Dart_KernelCompilationStatus_Ok) { |
544 | FATAL1( |
545 | "An error occurred in the CFE while accepting the most recent" |
546 | " compilation results: %s" , |
547 | result.error); |
548 | } |
549 | } |
550 | |
551 | // If [root_script_url] is null, attempt to load from [kernel_buffer]. |
552 | bool IsolateGroupReloadContext::Reload(bool force_reload, |
553 | const char* root_script_url, |
554 | const char* packages_url, |
555 | const uint8_t* kernel_buffer, |
556 | intptr_t kernel_buffer_size) { |
557 | TIMELINE_SCOPE(Reload); |
558 | |
559 | Thread* thread = Thread::Current(); |
560 | |
561 | // All isolates have the same sources, so all of them have the same libraries. |
562 | // We use the [first_isolate_] here to determine which of libraries have |
563 | // changed. |
564 | ASSERT(first_isolate_ == nullptr); |
565 | first_isolate_ = thread->isolate(); |
566 | |
567 | // All isolates within an isolate group need to share one heap. |
568 | // TODO(dartbug.com/36097): Remove this assert once the shared heap CL has |
569 | // landed. |
570 | RELEASE_ASSERT(!FLAG_enable_isolate_groups); |
571 | Heap* heap = first_isolate_->heap(); |
572 | |
573 | num_old_libs_ = GrowableObjectArray::Handle( |
574 | Z, first_isolate_->object_store()->libraries()) |
575 | .Length(); |
576 | |
577 | // Grab root library before calling CheckpointBeforeReload. |
578 | GetRootLibUrl(root_script_url); |
579 | |
580 | std::unique_ptr<kernel::Program> kernel_program; |
581 | |
582 | // Reset stats. |
583 | num_received_libs_ = 0; |
584 | bytes_received_libs_ = 0; |
585 | num_received_classes_ = 0; |
586 | num_received_procedures_ = 0; |
587 | |
588 | bool did_kernel_compilation = false; |
589 | bool skip_reload = false; |
590 | { |
591 | // Load the kernel program and figure out the modified libraries. |
592 | intptr_t* p_num_received_classes = nullptr; |
593 | intptr_t* p_num_received_procedures = nullptr; |
594 | |
595 | // ReadKernelFromFile checks to see if the file at |
596 | // root_script_url is a valid .dill file. If that's the case, a Program* |
597 | // is returned. Otherwise, this is likely a source file that needs to be |
598 | // compiled, so ReadKernelFromFile returns NULL. |
599 | kernel_program = kernel::Program::ReadFromFile(root_script_url); |
600 | if (kernel_program != nullptr) { |
601 | num_received_libs_ = kernel_program->library_count(); |
602 | bytes_received_libs_ = kernel_program->kernel_data_size(); |
603 | p_num_received_classes = &num_received_classes_; |
604 | p_num_received_procedures = &num_received_procedures_; |
605 | } else { |
606 | if (kernel_buffer == NULL || kernel_buffer_size == 0) { |
607 | char* error = CompileToKernel(force_reload, packages_url, |
608 | &kernel_buffer, &kernel_buffer_size); |
609 | did_kernel_compilation = true; |
610 | if (error != nullptr) { |
611 | TIR_Print("---- LOAD FAILED, ABORTING RELOAD\n" ); |
612 | const auto& error_str = String::Handle(Z, String::New(error)); |
613 | free(error); |
614 | const ApiError& error = ApiError::Handle(Z, ApiError::New(error_str)); |
615 | AddReasonForCancelling(new Aborted(Z, error)); |
616 | ReportReasonsForCancelling(); |
617 | CommonFinalizeTail(num_old_libs_); |
618 | return false; |
619 | } |
620 | } |
621 | const auto& typed_data = ExternalTypedData::Handle( |
622 | Z, ExternalTypedData::NewFinalizeWithFree( |
623 | const_cast<uint8_t*>(kernel_buffer), kernel_buffer_size)); |
624 | kernel_program = kernel::Program::ReadFromTypedData(typed_data); |
625 | } |
626 | |
627 | ExternalTypedData& external_typed_data = |
628 | ExternalTypedData::Handle(Z, kernel_program.get()->typed_data()->raw()); |
629 | IsolateGroupSource* source = Isolate::Current()->source(); |
630 | source->add_loaded_blob(Z, external_typed_data); |
631 | |
632 | modified_libs_ = new (Z) BitVector(Z, num_old_libs_); |
633 | kernel::KernelLoader::FindModifiedLibraries( |
634 | kernel_program.get(), first_isolate_, modified_libs_, force_reload, |
635 | &skip_reload, p_num_received_classes, p_num_received_procedures); |
636 | modified_libs_transitive_ = new (Z) BitVector(Z, num_old_libs_); |
637 | BuildModifiedLibrariesClosure(modified_libs_); |
638 | |
639 | ASSERT(num_saved_libs_ == -1); |
640 | num_saved_libs_ = 0; |
641 | for (intptr_t i = 0; i < modified_libs_->length(); i++) { |
642 | if (!modified_libs_->Contains(i)) { |
643 | num_saved_libs_++; |
644 | } |
645 | } |
646 | } |
647 | |
648 | if (skip_reload) { |
649 | ASSERT(modified_libs_->IsEmpty()); |
650 | reload_skipped_ = true; |
651 | ReportOnJSON(js_, num_old_libs_); |
652 | |
653 | // If we use the CFE and performed a compilation, we need to notify that |
654 | // we have accepted the compilation to clear some state in the incremental |
655 | // compiler. |
656 | if (did_kernel_compilation) { |
657 | AcceptCompilation(thread); |
658 | } |
659 | TIR_Print("---- SKIPPING RELOAD (No libraries were modified)\n" ); |
660 | return false; |
661 | } |
662 | |
663 | TIR_Print("---- STARTING RELOAD\n" ); |
664 | |
665 | intptr_t number_of_isolates = 0; |
666 | isolate_group_->ForEachIsolate( |
667 | [&](Isolate* isolate) { number_of_isolates++; }); |
668 | |
669 | // Disable the background compiler while we are performing the reload. |
670 | ForEachIsolate( |
671 | [&](Isolate* isolate) { BackgroundCompiler::Disable(isolate); }); |
672 | |
673 | // Wait for any concurrent marking tasks to finish and turn off the |
674 | // concurrent marker during reload as we might be allocating new instances |
675 | // (constants) when loading the new kernel file and this could cause |
676 | // inconsistency between the saved class table and the new class table. |
677 | const bool old_concurrent_mark_flag = |
678 | heap->old_space()->enable_concurrent_mark(); |
679 | if (old_concurrent_mark_flag) { |
680 | heap->WaitForMarkerTasks(thread); |
681 | heap->old_space()->set_enable_concurrent_mark(false); |
682 | } |
683 | |
684 | // Ensure all functions on the stack have unoptimized code. |
685 | // Deoptimize all code that had optimizing decisions that are dependent on |
686 | // assumptions from field guards or CHA or deferred library prefixes. |
687 | // TODO(johnmccutchan): Deoptimizing dependent code here (before the reload) |
688 | // is paranoid. This likely can be moved to the commit phase. |
689 | ForEachIsolate([&](Isolate* isolate) { |
690 | isolate->reload_context()->EnsuredUnoptimizedCodeForStack(); |
691 | isolate->reload_context()->DeoptimizeDependentCode(); |
692 | isolate->reload_context()->ReloadPhase1AllocateStorageMapsAndCheckpoint(); |
693 | }); |
694 | // Renumbering the libraries has invalidated this. |
695 | modified_libs_ = nullptr; |
696 | modified_libs_transitive_ = nullptr; |
697 | |
698 | if (FLAG_gc_during_reload) { |
699 | // We use kLowMemory to force the GC to compact, which is more likely to |
700 | // discover untracked pointers (and other issues, like incorrect class |
701 | // table). |
702 | heap->CollectAllGarbage(Heap::kLowMemory); |
703 | } |
704 | |
705 | // Copy the size table for isolate group & class tables for each isolate. |
706 | { |
707 | TIMELINE_SCOPE(CheckpointClasses); |
708 | CheckpointSharedClassTable(); |
709 | ForEachIsolate([&](Isolate* isolate) { |
710 | isolate->reload_context()->CheckpointClasses(); |
711 | }); |
712 | } |
713 | |
714 | if (FLAG_gc_during_reload) { |
715 | // We use kLowMemory to force the GC to compact, which is more likely to |
716 | // discover untracked pointers (and other issues, like incorrect class |
717 | // table). |
718 | heap->CollectAllGarbage(Heap::kLowMemory); |
719 | } |
720 | |
721 | // We synchronously load the hot-reload kernel diff (which includes changed |
722 | // libraries and any libraries transitively depending on them). |
723 | // |
724 | // If loading the hot-reload diff succeeded we'll finalize the loading, which |
725 | // will either commit or reject the reload request. |
726 | const auto& results = Array::Handle(Z, Array::New(number_of_isolates)); |
727 | intptr_t isolateIndex = 0; |
728 | intptr_t load_errors = 0; |
729 | |
730 | auto& tmp = Object::Handle(Z); |
731 | ForEachIsolate([&](Isolate* isolate) { |
732 | tmp = isolate->reload_context()->ReloadPhase2LoadKernel( |
733 | kernel_program.get(), root_lib_url_); |
734 | if (tmp.IsError()) { |
735 | results.SetAt(isolateIndex, tmp); |
736 | load_errors++; |
737 | } |
738 | isolateIndex++; |
739 | }); |
740 | |
741 | const auto& result = Object::Handle(results.At(0)); |
742 | |
743 | if (load_errors > 0) { |
744 | TIR_Print("---- LOAD FAILED, ABORTING RELOAD\n" ); |
745 | |
746 | const auto& error = Error::Cast(result); |
747 | AddReasonForCancelling(new Aborted(Z, error)); |
748 | |
749 | DiscardSavedClassTable(/*is_rollback=*/true); |
750 | ForEachIsolate([&](Isolate* isolate) { |
751 | isolate->reload_context()->ReloadPhase4Rollback(); |
752 | }); |
753 | CommonFinalizeTail(num_old_libs_); |
754 | } else { |
755 | ASSERT(!reload_skipped_ && !reload_finalized_); |
756 | TIR_Print("---- LOAD SUCCEEDED\n" ); |
757 | |
758 | ForEachIsolate([&](Isolate* isolate) { |
759 | isolate->reload_context()->ReloadPhase3FinalizeLoading(); |
760 | }); |
761 | |
762 | if (FLAG_gc_during_reload) { |
763 | // We use kLowMemory to force the GC to compact, which is more likely to |
764 | // discover untracked pointers (and other issues, like incorrect class |
765 | // table). |
766 | heap->CollectAllGarbage(Heap::kLowMemory); |
767 | } |
768 | |
769 | if (!FLAG_reload_force_rollback && !HasReasonsForCancelling()) { |
770 | TIR_Print("---- COMMITTING RELOAD\n" ); |
771 | ForEachIsolate([&](Isolate* isolate) { |
772 | isolate->reload_context()->ReloadPhase4CommitPrepare(); |
773 | }); |
774 | bool discard_class_tables = true; |
775 | if (HasInstanceMorphers()) { |
776 | // Find all objects that need to be morphed (reallocated to a new size). |
777 | ObjectLocator locator(this); |
778 | { |
779 | HeapIterationScope iteration(Thread::Current()); |
780 | iteration.IterateObjects(&locator); |
781 | } |
782 | |
783 | // We are still using the old class table at this point. |
784 | if (FLAG_gc_during_reload) { |
785 | // We use kLowMemory to force the GC to compact, which is more likely |
786 | // to discover untracked pointers (and other issues, like incorrect |
787 | // class table). |
788 | heap->CollectAllGarbage(Heap::kLowMemory); |
789 | } |
790 | const intptr_t count = locator.count(); |
791 | if (count > 0) { |
792 | TIMELINE_SCOPE(MorphInstances); |
793 | |
794 | // While we are reallocating instances to their new size, the heap |
795 | // will contain a mix of instances with the old and new sizes that |
796 | // have the same cid. This makes the heap unwalkable until the |
797 | // "become" operation below replaces all the instances of the old |
798 | // size with forwarding corpses. Force heap growth to prevent size |
799 | // confusion during this period. |
800 | NoHeapGrowthControlScope scope; |
801 | // The HeapIterationScope above ensures no other GC tasks can be |
802 | // active. |
803 | ASSERT(HasNoTasks(heap)); |
804 | |
805 | const Array& before = Array::Handle(Z, Array::New(count)); |
806 | const Array& after = Array::Handle(Z, Array::New(count)); |
807 | |
808 | MorphInstancesPhase1Allocate(&locator, before, after); |
809 | { |
810 | // Apply the new class table before "become". Become will replace |
811 | // all the instances of the old size with forwarding corpses, then |
812 | // perform a heap walk to fix references to the forwarding corpses. |
813 | // During this heap walk, it will encounter instances of the new |
814 | // size, so it requires the new class table. |
815 | ASSERT(HasNoTasks(heap)); |
816 | |
817 | // We accepted the hot-reload and morphed instances. So now we can |
818 | // commit to the changed class table and deleted the saved one. |
819 | DiscardSavedClassTable(/*is_rollback=*/false); |
820 | ForEachIsolate([&](Isolate* isolate) { |
821 | isolate->reload_context()->DiscardSavedClassTable( |
822 | /*is_rollback=*/false); |
823 | }); |
824 | } |
825 | MorphInstancesPhase2Become(before, after); |
826 | |
827 | discard_class_tables = false; |
828 | } |
829 | // We are using the new class table now. |
830 | if (FLAG_gc_during_reload) { |
831 | // We use kLowMemory to force the GC to compact, which is more likely |
832 | // to discover untracked pointers (and other issues, like incorrect |
833 | // class table). |
834 | heap->CollectAllGarbage(Heap::kLowMemory); |
835 | } |
836 | } |
837 | if (discard_class_tables) { |
838 | DiscardSavedClassTable(/*is_rollback=*/false); |
839 | ForEachIsolate([&](Isolate* isolate) { |
840 | isolate->reload_context()->DiscardSavedClassTable( |
841 | /*is_rollback=*/false); |
842 | }); |
843 | } |
844 | ForEachIsolate([&](Isolate* isolate) { |
845 | isolate->reload_context()->ReloadPhase4CommitFinish(); |
846 | }); |
847 | TIR_Print("---- DONE COMMIT\n" ); |
848 | isolate_group_->set_last_reload_timestamp(reload_timestamp_); |
849 | } else { |
850 | TIR_Print("---- ROLLING BACK" ); |
851 | DiscardSavedClassTable(/*is_rollback=*/true); |
852 | ForEachIsolate([&](Isolate* isolate) { |
853 | isolate->reload_context()->ReloadPhase4Rollback(); |
854 | }); |
855 | } |
856 | |
857 | // ValidateReload mutates the direct subclass information and does |
858 | // not remove dead subclasses. Rebuild the direct subclass |
859 | // information from scratch. |
860 | ForEachIsolate([&](Isolate* isolate) { |
861 | isolate->reload_context()->RebuildDirectSubclasses(); |
862 | }); |
863 | const intptr_t final_library_count = |
864 | GrowableObjectArray::Handle(Z, |
865 | first_isolate_->object_store()->libraries()) |
866 | .Length(); |
867 | CommonFinalizeTail(final_library_count); |
868 | |
869 | // If we use the CFE and performed a compilation, we need to notify that |
870 | // we have accepted the compilation to clear some state in the incremental |
871 | // compiler. |
872 | if (did_kernel_compilation) { |
873 | AcceptCompilation(thread); |
874 | } |
875 | } |
876 | |
877 | // Re-enable the background compiler. Do this before propagating any errors. |
878 | ForEachIsolate( |
879 | [&](Isolate* isolate) { BackgroundCompiler::Enable(isolate); }); |
880 | |
881 | // Reenable concurrent marking if it was initially on. |
882 | if (old_concurrent_mark_flag) { |
883 | heap->old_space()->set_enable_concurrent_mark(true); |
884 | } |
885 | |
886 | bool success; |
887 | if (load_errors == 0 || HasReasonsForCancelling()) { |
888 | ReportSuccess(); |
889 | success = true; |
890 | } else { |
891 | ReportReasonsForCancelling(); |
892 | success = false; |
893 | } |
894 | |
895 | // Re-queue any shutdown requests so they can inform each isolate's own thread |
896 | // to shut down. |
897 | isolateIndex = 0; |
898 | ForEachIsolate([&](Isolate* isolate) { |
899 | tmp = results.At(isolateIndex); |
900 | if (tmp.IsUnwindError()) { |
901 | Isolate::KillIfExists(isolate, UnwindError::Cast(tmp).is_user_initiated() |
902 | ? Isolate::kKillMsg |
903 | : Isolate::kInternalKillMsg); |
904 | } |
905 | isolateIndex++; |
906 | }); |
907 | |
908 | return success; |
909 | } |
910 | |
911 | /// Copied in from https://dart-review.googlesource.com/c/sdk/+/77722. |
912 | static void PropagateLibraryModified( |
913 | const ZoneGrowableArray<ZoneGrowableArray<intptr_t>*>* imported_by, |
914 | intptr_t lib_index, |
915 | BitVector* modified_libs) { |
916 | ZoneGrowableArray<intptr_t>* dep_libs = (*imported_by)[lib_index]; |
917 | for (intptr_t i = 0; i < dep_libs->length(); i++) { |
918 | intptr_t dep_lib_index = (*dep_libs)[i]; |
919 | if (!modified_libs->Contains(dep_lib_index)) { |
920 | modified_libs->Add(dep_lib_index); |
921 | PropagateLibraryModified(imported_by, dep_lib_index, modified_libs); |
922 | } |
923 | } |
924 | } |
925 | |
926 | /// Copied in from https://dart-review.googlesource.com/c/sdk/+/77722. |
927 | void IsolateGroupReloadContext::BuildModifiedLibrariesClosure( |
928 | BitVector* modified_libs) { |
929 | const GrowableObjectArray& libs = |
930 | GrowableObjectArray::Handle(first_isolate_->object_store()->libraries()); |
931 | Library& lib = Library::Handle(); |
932 | intptr_t num_libs = libs.Length(); |
933 | |
934 | // Construct the imported-by graph. |
935 | ZoneGrowableArray<ZoneGrowableArray<intptr_t>*>* imported_by = new (zone_) |
936 | ZoneGrowableArray<ZoneGrowableArray<intptr_t>*>(zone_, num_libs); |
937 | imported_by->SetLength(num_libs); |
938 | for (intptr_t i = 0; i < num_libs; i++) { |
939 | (*imported_by)[i] = new (zone_) ZoneGrowableArray<intptr_t>(zone_, 0); |
940 | } |
941 | Array& ports = Array::Handle(); |
942 | Namespace& ns = Namespace::Handle(); |
943 | Library& target = Library::Handle(); |
944 | String& target_url = String::Handle(); |
945 | |
946 | for (intptr_t lib_idx = 0; lib_idx < num_libs; lib_idx++) { |
947 | lib ^= libs.At(lib_idx); |
948 | ASSERT(lib_idx == lib.index()); |
949 | if (lib.is_dart_scheme()) { |
950 | // We don't care about imports among dart scheme libraries. |
951 | continue; |
952 | } |
953 | |
954 | // Add imports to the import-by graph. |
955 | ports = lib.imports(); |
956 | for (intptr_t import_idx = 0; import_idx < ports.Length(); import_idx++) { |
957 | ns ^= ports.At(import_idx); |
958 | if (!ns.IsNull()) { |
959 | target = ns.library(); |
960 | target_url = target.url(); |
961 | if (!target_url.StartsWith(Symbols::DartExtensionScheme())) { |
962 | (*imported_by)[target.index()]->Add(lib.index()); |
963 | } |
964 | } |
965 | } |
966 | |
967 | // Add exports to the import-by graph. |
968 | ports = lib.exports(); |
969 | for (intptr_t export_idx = 0; export_idx < ports.Length(); export_idx++) { |
970 | ns ^= ports.At(export_idx); |
971 | if (!ns.IsNull()) { |
972 | target = ns.library(); |
973 | (*imported_by)[target.index()]->Add(lib.index()); |
974 | } |
975 | } |
976 | |
977 | // Add prefixed imports to the import-by graph. |
978 | DictionaryIterator entries(lib); |
979 | Object& entry = Object::Handle(); |
980 | LibraryPrefix& prefix = LibraryPrefix::Handle(); |
981 | while (entries.HasNext()) { |
982 | entry = entries.GetNext(); |
983 | if (entry.IsLibraryPrefix()) { |
984 | prefix ^= entry.raw(); |
985 | ports = prefix.imports(); |
986 | for (intptr_t import_idx = 0; import_idx < ports.Length(); |
987 | import_idx++) { |
988 | ns ^= ports.At(import_idx); |
989 | if (!ns.IsNull()) { |
990 | target = ns.library(); |
991 | (*imported_by)[target.index()]->Add(lib.index()); |
992 | } |
993 | } |
994 | } |
995 | } |
996 | } |
997 | |
998 | for (intptr_t lib_idx = 0; lib_idx < num_libs; lib_idx++) { |
999 | lib ^= libs.At(lib_idx); |
1000 | if (lib.is_dart_scheme() || modified_libs_transitive_->Contains(lib_idx)) { |
1001 | // We don't consider dart scheme libraries during reload. If |
1002 | // the modified libs set already contains this library, then we |
1003 | // have already visited it. |
1004 | continue; |
1005 | } |
1006 | if (modified_libs->Contains(lib_idx)) { |
1007 | modified_libs_transitive_->Add(lib_idx); |
1008 | PropagateLibraryModified(imported_by, lib_idx, modified_libs_transitive_); |
1009 | } |
1010 | } |
1011 | } |
1012 | |
1013 | void IsolateGroupReloadContext::GetRootLibUrl(const char* root_script_url) { |
1014 | const auto& old_root_lib = |
1015 | Library::Handle(first_isolate_->object_store()->root_library()); |
1016 | ASSERT(!old_root_lib.IsNull()); |
1017 | const auto& old_root_lib_url = String::Handle(old_root_lib.url()); |
1018 | |
1019 | // Root library url. |
1020 | if (root_script_url != nullptr) { |
1021 | root_lib_url_ = String::New(root_script_url); |
1022 | } else { |
1023 | root_lib_url_ = old_root_lib_url.raw(); |
1024 | } |
1025 | |
1026 | // Check to see if the base url of the loaded libraries has moved. |
1027 | if (!old_root_lib_url.Equals(root_lib_url_)) { |
1028 | const char* old_root_library_url_c = old_root_lib_url.ToCString(); |
1029 | const char* root_library_url_c = root_lib_url_.ToCString(); |
1030 | const intptr_t common_suffix_length = |
1031 | CommonSuffixLength(root_library_url_c, old_root_library_url_c); |
1032 | root_url_prefix_ = String::SubString( |
1033 | root_lib_url_, 0, root_lib_url_.Length() - common_suffix_length + 1); |
1034 | old_root_url_prefix_ = |
1035 | String::SubString(old_root_lib_url, 0, |
1036 | old_root_lib_url.Length() - common_suffix_length + 1); |
1037 | } |
1038 | } |
1039 | |
1040 | char* IsolateGroupReloadContext::CompileToKernel(bool force_reload, |
1041 | const char* packages_url, |
1042 | const uint8_t** kernel_buffer, |
1043 | intptr_t* kernel_buffer_size) { |
1044 | Dart_SourceFile* modified_scripts = nullptr; |
1045 | intptr_t modified_scripts_count = 0; |
1046 | FindModifiedSources(force_reload, &modified_scripts, &modified_scripts_count, |
1047 | packages_url); |
1048 | |
1049 | Dart_KernelCompilationResult retval = {}; |
1050 | { |
1051 | const char* root_lib_url = root_lib_url_.ToCString(); |
1052 | TransitionVMToNative transition(Thread::Current()); |
1053 | retval = KernelIsolate::CompileToKernel(root_lib_url, nullptr, 0, |
1054 | modified_scripts_count, |
1055 | modified_scripts, true, nullptr); |
1056 | } |
1057 | if (retval.status != Dart_KernelCompilationStatus_Ok) { |
1058 | if (retval.kernel != nullptr) { |
1059 | free(retval.kernel); |
1060 | } |
1061 | return retval.error; |
1062 | } |
1063 | *kernel_buffer = retval.kernel; |
1064 | *kernel_buffer_size = retval.kernel_size; |
1065 | return nullptr; |
1066 | } |
1067 | |
1068 | void IsolateReloadContext::ReloadPhase1AllocateStorageMapsAndCheckpoint() { |
1069 | // Preallocate storage for maps. |
1070 | old_classes_set_storage_ = |
1071 | HashTables::New<UnorderedHashSet<ClassMapTraits> >(4); |
1072 | class_map_storage_ = HashTables::New<UnorderedHashMap<ClassMapTraits> >(4); |
1073 | removed_class_set_storage_ = |
1074 | HashTables::New<UnorderedHashSet<ClassMapTraits> >(4); |
1075 | old_libraries_set_storage_ = |
1076 | HashTables::New<UnorderedHashSet<LibraryMapTraits> >(4); |
1077 | library_map_storage_ = |
1078 | HashTables::New<UnorderedHashMap<LibraryMapTraits> >(4); |
1079 | become_map_storage_ = HashTables::New<UnorderedHashMap<BecomeMapTraits> >(4); |
1080 | // Keep a separate array for enum mappings to avoid having to invoke |
1081 | // hashCode on the instances. |
1082 | become_enum_mappings_ = GrowableObjectArray::New(Heap::kOld); |
1083 | |
1084 | // While reloading everything we do must be reversible so that we can abort |
1085 | // safely if the reload fails. This function stashes things to the side and |
1086 | // prepares the isolate for the reload attempt. |
1087 | { |
1088 | TIMELINE_SCOPE(Checkpoint); |
1089 | CheckpointLibraries(); |
1090 | } |
1091 | } |
1092 | |
1093 | ObjectPtr IsolateReloadContext::ReloadPhase2LoadKernel( |
1094 | kernel::Program* program, |
1095 | const String& root_lib_url) { |
1096 | Thread* thread = Thread::Current(); |
1097 | |
1098 | const Object& tmp = kernel::KernelLoader::LoadEntireProgram(program); |
1099 | if (tmp.IsError()) { |
1100 | return tmp.raw(); |
1101 | } |
1102 | |
1103 | // If main method disappeared or were not there to begin with then |
1104 | // KernelLoader will return null. In this case lookup library by |
1105 | // URL. |
1106 | auto& lib = Library::Handle(Library::RawCast(tmp.raw())); |
1107 | if (lib.IsNull()) { |
1108 | lib = Library::LookupLibrary(thread, root_lib_url); |
1109 | } |
1110 | isolate_->object_store()->set_root_library(lib); |
1111 | return Object::null(); |
1112 | } |
1113 | |
1114 | void IsolateReloadContext::ReloadPhase3FinalizeLoading() { |
1115 | BuildLibraryMapping(); |
1116 | BuildRemovedClassesSet(); |
1117 | ValidateReload(); |
1118 | } |
1119 | |
1120 | void IsolateReloadContext::ReloadPhase4CommitPrepare() { |
1121 | CommitBeforeInstanceMorphing(); |
1122 | } |
1123 | |
1124 | void IsolateReloadContext::ReloadPhase4CommitFinish() { |
1125 | CommitAfterInstanceMorphing(); |
1126 | PostCommit(); |
1127 | } |
1128 | |
1129 | void IsolateReloadContext::ReloadPhase4Rollback() { |
1130 | RollbackClasses(); |
1131 | RollbackLibraries(); |
1132 | } |
1133 | |
1134 | void IsolateReloadContext::RegisterClass(const Class& new_cls) { |
1135 | const Class& old_cls = Class::Handle(OldClassOrNull(new_cls)); |
1136 | if (old_cls.IsNull()) { |
1137 | if (new_cls.IsTopLevel()) { |
1138 | I->class_table()->RegisterTopLevel(new_cls); |
1139 | } else { |
1140 | I->class_table()->Register(new_cls); |
1141 | } |
1142 | |
1143 | if (FLAG_identity_reload) { |
1144 | TIR_Print("Could not find replacement class for %s\n" , |
1145 | new_cls.ToCString()); |
1146 | UNREACHABLE(); |
1147 | } |
1148 | |
1149 | // New class maps to itself. |
1150 | AddClassMapping(new_cls, new_cls); |
1151 | return; |
1152 | } |
1153 | VTIR_Print("Registering class: %s\n" , new_cls.ToCString()); |
1154 | new_cls.set_id(old_cls.id()); |
1155 | I->class_table()->SetAt(old_cls.id(), new_cls.raw()); |
1156 | if (!old_cls.is_enum_class()) { |
1157 | new_cls.CopyCanonicalConstants(old_cls); |
1158 | } |
1159 | new_cls.CopyDeclarationType(old_cls); |
1160 | AddBecomeMapping(old_cls, new_cls); |
1161 | AddClassMapping(new_cls, old_cls); |
1162 | } |
1163 | |
1164 | void IsolateGroupReloadContext::CommonFinalizeTail( |
1165 | intptr_t final_library_count) { |
1166 | RELEASE_ASSERT(!reload_finalized_); |
1167 | ReportOnJSON(js_, final_library_count); |
1168 | reload_finalized_ = true; |
1169 | } |
1170 | |
1171 | void IsolateGroupReloadContext::ReportOnJSON(JSONStream* stream, |
1172 | intptr_t final_library_count) { |
1173 | JSONObject jsobj(stream); |
1174 | jsobj.AddProperty("type" , "ReloadReport" ); |
1175 | jsobj.AddProperty("success" , reload_skipped_ || !HasReasonsForCancelling()); |
1176 | { |
1177 | if (HasReasonsForCancelling()) { |
1178 | // Reload was rejected. |
1179 | JSONArray array(&jsobj, "notices" ); |
1180 | for (intptr_t i = 0; i < reasons_to_cancel_reload_.length(); i++) { |
1181 | ReasonForCancelling* reason = reasons_to_cancel_reload_.At(i); |
1182 | reason->AppendTo(&array); |
1183 | } |
1184 | return; |
1185 | } |
1186 | |
1187 | JSONObject details(&jsobj, "details" ); |
1188 | details.AddProperty("finalLibraryCount" , final_library_count); |
1189 | details.AddProperty("receivedLibraryCount" , num_received_libs_); |
1190 | details.AddProperty("receivedLibrariesBytes" , bytes_received_libs_); |
1191 | details.AddProperty("receivedClassesCount" , num_received_classes_); |
1192 | details.AddProperty("receivedProceduresCount" , num_received_procedures_); |
1193 | if (reload_skipped_) { |
1194 | // Reload was skipped. |
1195 | details.AddProperty("savedLibraryCount" , final_library_count); |
1196 | details.AddProperty("loadedLibraryCount" , static_cast<intptr_t>(0)); |
1197 | } else { |
1198 | // Reload was successful. |
1199 | const intptr_t loaded_library_count = |
1200 | final_library_count - num_saved_libs_; |
1201 | details.AddProperty("savedLibraryCount" , num_saved_libs_); |
1202 | details.AddProperty("loadedLibraryCount" , loaded_library_count); |
1203 | JSONArray array(&jsobj, "shapeChangeMappings" ); |
1204 | for (intptr_t i = 0; i < instance_morphers_.length(); i++) { |
1205 | instance_morphers_.At(i)->AppendTo(&array); |
1206 | } |
1207 | } |
1208 | } |
1209 | } |
1210 | |
1211 | void IsolateReloadContext::EnsuredUnoptimizedCodeForStack() { |
1212 | TIMELINE_SCOPE(EnsuredUnoptimizedCodeForStack); |
1213 | StackFrameIterator it(ValidationPolicy::kDontValidateFrames, |
1214 | Thread::Current(), |
1215 | StackFrameIterator::kNoCrossThreadIteration); |
1216 | |
1217 | Function& func = Function::Handle(); |
1218 | while (it.HasNextFrame()) { |
1219 | StackFrame* frame = it.NextFrame(); |
1220 | if (frame->IsDartFrame() && !frame->is_interpreted()) { |
1221 | func = frame->LookupDartFunction(); |
1222 | ASSERT(!func.IsNull()); |
1223 | // Force-optimized functions don't need unoptimized code because their |
1224 | // optimized code cannot deopt. |
1225 | if (!func.ForceOptimize()) { |
1226 | func.EnsureHasCompiledUnoptimizedCode(); |
1227 | } |
1228 | } |
1229 | } |
1230 | } |
1231 | |
1232 | void IsolateReloadContext::DeoptimizeDependentCode() { |
1233 | TIMELINE_SCOPE(DeoptimizeDependentCode); |
1234 | ClassTable* class_table = I->class_table(); |
1235 | |
1236 | const intptr_t bottom = Dart::vm_isolate()->class_table()->NumCids(); |
1237 | const intptr_t top = I->class_table()->NumCids(); |
1238 | Class& cls = Class::Handle(); |
1239 | Array& fields = Array::Handle(); |
1240 | Field& field = Field::Handle(); |
1241 | for (intptr_t cls_idx = bottom; cls_idx < top; cls_idx++) { |
1242 | if (!class_table->HasValidClassAt(cls_idx)) { |
1243 | // Skip. |
1244 | continue; |
1245 | } |
1246 | |
1247 | // Deoptimize CHA code. |
1248 | cls = class_table->At(cls_idx); |
1249 | ASSERT(!cls.IsNull()); |
1250 | |
1251 | cls.DisableAllCHAOptimizedCode(); |
1252 | |
1253 | // Deoptimize field guard code. |
1254 | fields = cls.fields(); |
1255 | ASSERT(!fields.IsNull()); |
1256 | for (intptr_t field_idx = 0; field_idx < fields.Length(); field_idx++) { |
1257 | field = Field::RawCast(fields.At(field_idx)); |
1258 | ASSERT(!field.IsNull()); |
1259 | field.DeoptimizeDependentCode(); |
1260 | } |
1261 | } |
1262 | |
1263 | DeoptimizeTypeTestingStubs(); |
1264 | |
1265 | // TODO(rmacnak): Also call LibraryPrefix::InvalidateDependentCode. |
1266 | } |
1267 | |
1268 | void IsolateGroupReloadContext::CheckpointSharedClassTable() { |
1269 | // Copy the size table for isolate group. |
1270 | intptr_t* saved_size_table = nullptr; |
1271 | shared_class_table_->CopyBeforeHotReload(&saved_size_table, &saved_num_cids_); |
1272 | |
1273 | Thread* thread = Thread::Current(); |
1274 | { |
1275 | NoSafepointScope no_safepoint_scope(thread); |
1276 | |
1277 | // The saved_size_table_ will now become source of truth for GC. |
1278 | saved_size_table_.store(saved_size_table, std::memory_order_release); |
1279 | } |
1280 | |
1281 | // But the concurrent sweeper may still be reading from the old table. |
1282 | thread->heap()->WaitForSweeperTasks(thread); |
1283 | |
1284 | // Now we can clear the old table. This satisfies asserts during class |
1285 | // registration and encourages fast failure if we use the wrong table |
1286 | // for GC during reload, but isn't strictly needed for correctness. |
1287 | shared_class_table_->ResetBeforeHotReload(); |
1288 | } |
1289 | |
1290 | void IsolateReloadContext::CheckpointClasses() { |
1291 | TIR_Print("---- CHECKPOINTING CLASSES\n" ); |
1292 | // Checkpoint classes before a reload. We need to copy the following: |
1293 | // 1) The size of the class table. |
1294 | // 2) The class table itself. |
1295 | // For efficiency, we build a set of classes before the reload. This set |
1296 | // is used to pair new classes with old classes. |
1297 | |
1298 | // Copy the class table for isolate. |
1299 | ClassTable* class_table = I->class_table(); |
1300 | ClassPtr* saved_class_table = nullptr; |
1301 | ClassPtr* saved_tlc_class_table = nullptr; |
1302 | class_table->CopyBeforeHotReload(&saved_class_table, &saved_tlc_class_table, |
1303 | &saved_num_cids_, &saved_num_tlc_cids_); |
1304 | |
1305 | // Copy classes into saved_class_table_ first. Make sure there are no |
1306 | // safepoints until saved_class_table_ is filled up and saved so class raw |
1307 | // pointers in saved_class_table_ are properly visited by GC. |
1308 | { |
1309 | NoSafepointScope no_safepoint_scope(Thread::Current()); |
1310 | |
1311 | // The saved_class_table_ is now source of truth for GC. |
1312 | saved_class_table_.store(saved_class_table, std::memory_order_release); |
1313 | saved_tlc_class_table_.store(saved_tlc_class_table, |
1314 | std::memory_order_release); |
1315 | |
1316 | // We can therefore wipe out all of the old entries (if that table is used |
1317 | // for GC during the hot-reload we have a bug). |
1318 | class_table->ResetBeforeHotReload(); |
1319 | } |
1320 | |
1321 | // Add classes to the set. Set is stored in the Array, so adding an element |
1322 | // may allocate Dart object on the heap and trigger GC. |
1323 | Class& cls = Class::Handle(); |
1324 | UnorderedHashSet<ClassMapTraits> old_classes_set(old_classes_set_storage_); |
1325 | for (intptr_t i = 0; i < saved_num_cids_; i++) { |
1326 | if (class_table->IsValidIndex(i) && class_table->HasValidClassAt(i)) { |
1327 | if (i != kFreeListElement && i != kForwardingCorpse) { |
1328 | cls = class_table->At(i); |
1329 | bool already_present = old_classes_set.Insert(cls); |
1330 | ASSERT(!already_present); |
1331 | } |
1332 | } |
1333 | } |
1334 | for (intptr_t i = 0; i < saved_num_tlc_cids_; i++) { |
1335 | const intptr_t cid = ClassTable::CidFromTopLevelIndex(i); |
1336 | if (class_table->IsValidIndex(cid) && class_table->HasValidClassAt(cid)) { |
1337 | cls = class_table->At(cid); |
1338 | bool already_present = old_classes_set.Insert(cls); |
1339 | ASSERT(!already_present); |
1340 | } |
1341 | } |
1342 | old_classes_set_storage_ = old_classes_set.Release().raw(); |
1343 | TIR_Print("---- System had %" Pd " classes\n" , saved_num_cids_); |
1344 | } |
1345 | |
1346 | Dart_FileModifiedCallback IsolateGroupReloadContext::file_modified_callback_ = |
1347 | nullptr; |
1348 | |
1349 | bool IsolateGroupReloadContext::ScriptModifiedSince(const Script& script, |
1350 | int64_t since) { |
1351 | if (IsolateGroupReloadContext::file_modified_callback_ == NULL) { |
1352 | return true; |
1353 | } |
1354 | // We use the resolved url to determine if the script has been modified. |
1355 | const String& url = String::Handle(script.resolved_url()); |
1356 | const char* url_chars = url.ToCString(); |
1357 | return (*IsolateGroupReloadContext::file_modified_callback_)(url_chars, |
1358 | since); |
1359 | } |
1360 | |
1361 | static bool ContainsScriptUri(const GrowableArray<const char*>& seen_uris, |
1362 | const char* uri) { |
1363 | for (intptr_t i = 0; i < seen_uris.length(); i++) { |
1364 | const char* seen_uri = seen_uris.At(i); |
1365 | size_t seen_len = strlen(seen_uri); |
1366 | if (seen_len != strlen(uri)) { |
1367 | continue; |
1368 | } else if (strncmp(seen_uri, uri, seen_len) == 0) { |
1369 | return true; |
1370 | } |
1371 | } |
1372 | return false; |
1373 | } |
1374 | |
1375 | void IsolateGroupReloadContext::FindModifiedSources( |
1376 | bool force_reload, |
1377 | Dart_SourceFile** modified_sources, |
1378 | intptr_t* count, |
1379 | const char* packages_url) { |
1380 | const int64_t last_reload = isolate_group_->last_reload_timestamp(); |
1381 | GrowableArray<const char*> modified_sources_uris; |
1382 | const auto& libs = |
1383 | GrowableObjectArray::Handle(first_isolate_->object_store()->libraries()); |
1384 | Library& lib = Library::Handle(Z); |
1385 | Array& scripts = Array::Handle(Z); |
1386 | Script& script = Script::Handle(Z); |
1387 | String& uri = String::Handle(Z); |
1388 | |
1389 | for (intptr_t lib_idx = 0; lib_idx < libs.Length(); lib_idx++) { |
1390 | lib ^= libs.At(lib_idx); |
1391 | if (lib.is_dart_scheme()) { |
1392 | // We don't consider dart scheme libraries during reload. |
1393 | continue; |
1394 | } |
1395 | scripts = lib.LoadedScripts(); |
1396 | for (intptr_t script_idx = 0; script_idx < scripts.Length(); script_idx++) { |
1397 | script ^= scripts.At(script_idx); |
1398 | uri = script.url(); |
1399 | const bool dart_scheme = uri.StartsWith(Symbols::DartScheme()); |
1400 | if (dart_scheme) { |
1401 | // If a user-defined class mixes in a mixin from dart:*, it's list of |
1402 | // scripts will have a dart:* script as well. We don't consider those |
1403 | // during reload. |
1404 | continue; |
1405 | } |
1406 | if (ContainsScriptUri(modified_sources_uris, uri.ToCString())) { |
1407 | // We've already accounted for this script in a prior library. |
1408 | continue; |
1409 | } |
1410 | |
1411 | if (force_reload || ScriptModifiedSince(script, last_reload)) { |
1412 | modified_sources_uris.Add(uri.ToCString()); |
1413 | } |
1414 | } |
1415 | } |
1416 | |
1417 | // In addition to all sources, we need to check if the .packages file |
1418 | // contents have been modified. |
1419 | if (packages_url != NULL) { |
1420 | if (IsolateGroupReloadContext::file_modified_callback_ == NULL || |
1421 | (*IsolateGroupReloadContext::file_modified_callback_)(packages_url, |
1422 | last_reload)) { |
1423 | modified_sources_uris.Add(packages_url); |
1424 | } |
1425 | } |
1426 | |
1427 | *count = modified_sources_uris.length(); |
1428 | if (*count == 0) { |
1429 | return; |
1430 | } |
1431 | |
1432 | *modified_sources = Z->Alloc<Dart_SourceFile>(*count); |
1433 | for (intptr_t i = 0; i < *count; ++i) { |
1434 | (*modified_sources)[i].uri = modified_sources_uris[i]; |
1435 | (*modified_sources)[i].source = NULL; |
1436 | } |
1437 | } |
1438 | |
1439 | void IsolateReloadContext::CheckpointLibraries() { |
1440 | TIMELINE_SCOPE(CheckpointLibraries); |
1441 | TIR_Print("---- CHECKPOINTING LIBRARIES\n" ); |
1442 | // Save the root library in case we abort the reload. |
1443 | const Library& root_lib = Library::Handle(object_store()->root_library()); |
1444 | saved_root_library_ = root_lib.raw(); |
1445 | |
1446 | // Save the old libraries array in case we abort the reload. |
1447 | const GrowableObjectArray& libs = |
1448 | GrowableObjectArray::Handle(object_store()->libraries()); |
1449 | saved_libraries_ = libs.raw(); |
1450 | |
1451 | // Make a filtered copy of the old libraries array. Keep "clean" libraries |
1452 | // that we will use instead of reloading. |
1453 | const GrowableObjectArray& new_libs = |
1454 | GrowableObjectArray::Handle(GrowableObjectArray::New(Heap::kOld)); |
1455 | Library& lib = Library::Handle(); |
1456 | UnorderedHashSet<LibraryMapTraits> old_libraries_set( |
1457 | old_libraries_set_storage_); |
1458 | |
1459 | group_reload_context_->saved_libs_transitive_updated_ = new (Z) |
1460 | BitVector(Z, group_reload_context_->modified_libs_transitive_->length()); |
1461 | for (intptr_t i = 0; i < libs.Length(); i++) { |
1462 | lib ^= libs.At(i); |
1463 | if (group_reload_context_->modified_libs_->Contains(i)) { |
1464 | // We are going to reload this library. Clear the index. |
1465 | lib.set_index(-1); |
1466 | } else { |
1467 | // We are preserving this library across the reload, assign its new index |
1468 | lib.set_index(new_libs.Length()); |
1469 | new_libs.Add(lib, Heap::kOld); |
1470 | |
1471 | if (group_reload_context_->modified_libs_transitive_->Contains(i)) { |
1472 | // Remember the new index. |
1473 | group_reload_context_->saved_libs_transitive_updated_->Add(lib.index()); |
1474 | } |
1475 | } |
1476 | // Add old library to old libraries set. |
1477 | bool already_present = old_libraries_set.Insert(lib); |
1478 | ASSERT(!already_present); |
1479 | } |
1480 | old_libraries_set_storage_ = old_libraries_set.Release().raw(); |
1481 | |
1482 | // Reset the registered libraries to the filtered array. |
1483 | Library::RegisterLibraries(Thread::Current(), new_libs); |
1484 | // Reset the root library to null. |
1485 | object_store()->set_root_library(Library::Handle()); |
1486 | } |
1487 | |
1488 | void IsolateReloadContext::RollbackClasses() { |
1489 | TIR_Print("---- ROLLING BACK CLASS TABLE\n" ); |
1490 | ASSERT((saved_num_cids_ + saved_num_tlc_cids_) > 0); |
1491 | ASSERT(saved_class_table_.load(std::memory_order_relaxed) != nullptr); |
1492 | ASSERT(saved_tlc_class_table_.load(std::memory_order_relaxed) != nullptr); |
1493 | |
1494 | DiscardSavedClassTable(/*is_rollback=*/true); |
1495 | } |
1496 | |
1497 | void IsolateReloadContext::RollbackLibraries() { |
1498 | TIR_Print("---- ROLLING BACK LIBRARY CHANGES\n" ); |
1499 | Thread* thread = Thread::Current(); |
1500 | Library& lib = Library::Handle(); |
1501 | const auto& saved_libs = GrowableObjectArray::Handle(Z, saved_libraries_); |
1502 | if (!saved_libs.IsNull()) { |
1503 | for (intptr_t i = 0; i < saved_libs.Length(); i++) { |
1504 | lib = Library::RawCast(saved_libs.At(i)); |
1505 | // Restore indexes that were modified in CheckpointLibraries. |
1506 | lib.set_index(i); |
1507 | } |
1508 | |
1509 | // Reset the registered libraries to the filtered array. |
1510 | Library::RegisterLibraries(thread, saved_libs); |
1511 | } |
1512 | |
1513 | Library& saved_root_lib = Library::Handle(Z, saved_root_library_); |
1514 | if (!saved_root_lib.IsNull()) { |
1515 | object_store()->set_root_library(saved_root_lib); |
1516 | } |
1517 | |
1518 | saved_root_library_ = Library::null(); |
1519 | saved_libraries_ = GrowableObjectArray::null(); |
1520 | } |
1521 | |
1522 | #ifdef DEBUG |
1523 | void IsolateReloadContext::VerifyMaps() { |
1524 | TIMELINE_SCOPE(VerifyMaps); |
1525 | Class& cls = Class::Handle(); |
1526 | Class& new_cls = Class::Handle(); |
1527 | Class& cls2 = Class::Handle(); |
1528 | |
1529 | // Verify that two old classes aren't both mapped to the same new |
1530 | // class. This could happen is the IsSameClass function is broken. |
1531 | UnorderedHashMap<ClassMapTraits> class_map(class_map_storage_); |
1532 | UnorderedHashMap<ClassMapTraits> reverse_class_map( |
1533 | HashTables::New<UnorderedHashMap<ClassMapTraits> >( |
1534 | class_map.NumOccupied())); |
1535 | { |
1536 | UnorderedHashMap<ClassMapTraits>::Iterator it(&class_map); |
1537 | while (it.MoveNext()) { |
1538 | const intptr_t entry = it.Current(); |
1539 | new_cls = Class::RawCast(class_map.GetKey(entry)); |
1540 | cls = Class::RawCast(class_map.GetPayload(entry, 0)); |
1541 | cls2 ^= reverse_class_map.GetOrNull(new_cls); |
1542 | if (!cls2.IsNull()) { |
1543 | OS::PrintErr( |
1544 | "Classes '%s' and '%s' are distinct classes but both map " |
1545 | " to class '%s'\n" , |
1546 | cls.ToCString(), cls2.ToCString(), new_cls.ToCString()); |
1547 | UNREACHABLE(); |
1548 | } |
1549 | bool update = reverse_class_map.UpdateOrInsert(cls, new_cls); |
1550 | ASSERT(!update); |
1551 | } |
1552 | } |
1553 | class_map.Release(); |
1554 | reverse_class_map.Release(); |
1555 | } |
1556 | #endif |
1557 | |
1558 | void IsolateReloadContext::CommitBeforeInstanceMorphing() { |
1559 | TIMELINE_SCOPE(Commit); |
1560 | |
1561 | #ifdef DEBUG |
1562 | VerifyMaps(); |
1563 | #endif |
1564 | |
1565 | // Copy over certain properties of libraries, e.g. is the library |
1566 | // debuggable? |
1567 | { |
1568 | TIMELINE_SCOPE(CopyLibraryBits); |
1569 | Library& lib = Library::Handle(); |
1570 | Library& new_lib = Library::Handle(); |
1571 | |
1572 | UnorderedHashMap<LibraryMapTraits> lib_map(library_map_storage_); |
1573 | |
1574 | { |
1575 | // Reload existing libraries. |
1576 | UnorderedHashMap<LibraryMapTraits>::Iterator it(&lib_map); |
1577 | |
1578 | while (it.MoveNext()) { |
1579 | const intptr_t entry = it.Current(); |
1580 | ASSERT(entry != -1); |
1581 | new_lib = Library::RawCast(lib_map.GetKey(entry)); |
1582 | lib = Library::RawCast(lib_map.GetPayload(entry, 0)); |
1583 | new_lib.set_debuggable(lib.IsDebuggable()); |
1584 | // Native extension support. |
1585 | new_lib.set_native_entry_resolver(lib.native_entry_resolver()); |
1586 | new_lib.set_native_entry_symbol_resolver( |
1587 | lib.native_entry_symbol_resolver()); |
1588 | } |
1589 | } |
1590 | |
1591 | // Release the library map. |
1592 | lib_map.Release(); |
1593 | } |
1594 | |
1595 | { |
1596 | TIMELINE_SCOPE(CopyStaticFieldsAndPatchFieldsAndFunctions); |
1597 | // Copy static field values from the old classes to the new classes. |
1598 | // Patch fields and functions in the old classes so that they retain |
1599 | // the old script. |
1600 | Class& old_cls = Class::Handle(); |
1601 | Class& new_cls = Class::Handle(); |
1602 | UnorderedHashMap<ClassMapTraits> class_map(class_map_storage_); |
1603 | |
1604 | { |
1605 | UnorderedHashMap<ClassMapTraits>::Iterator it(&class_map); |
1606 | while (it.MoveNext()) { |
1607 | const intptr_t entry = it.Current(); |
1608 | new_cls = Class::RawCast(class_map.GetKey(entry)); |
1609 | old_cls = Class::RawCast(class_map.GetPayload(entry, 0)); |
1610 | if (new_cls.raw() != old_cls.raw()) { |
1611 | ASSERT(new_cls.is_enum_class() == old_cls.is_enum_class()); |
1612 | if (new_cls.is_enum_class() && new_cls.is_finalized()) { |
1613 | new_cls.ReplaceEnum(this, old_cls); |
1614 | } else { |
1615 | new_cls.CopyStaticFieldValues(this, old_cls); |
1616 | } |
1617 | old_cls.PatchFieldsAndFunctions(); |
1618 | old_cls.MigrateImplicitStaticClosures(this, new_cls); |
1619 | } |
1620 | } |
1621 | } |
1622 | |
1623 | class_map.Release(); |
1624 | |
1625 | { |
1626 | UnorderedHashSet<ClassMapTraits> removed_class_set( |
1627 | removed_class_set_storage_); |
1628 | UnorderedHashSet<ClassMapTraits>::Iterator it(&removed_class_set); |
1629 | while (it.MoveNext()) { |
1630 | const intptr_t entry = it.Current(); |
1631 | old_cls ^= removed_class_set.GetKey(entry); |
1632 | old_cls.PatchFieldsAndFunctions(); |
1633 | } |
1634 | removed_class_set.Release(); |
1635 | } |
1636 | } |
1637 | |
1638 | { |
1639 | TIMELINE_SCOPE(UpdateLibrariesArray); |
1640 | // Update the libraries array. |
1641 | Library& lib = Library::Handle(); |
1642 | const GrowableObjectArray& libs = |
1643 | GrowableObjectArray::Handle(I->object_store()->libraries()); |
1644 | for (intptr_t i = 0; i < libs.Length(); i++) { |
1645 | lib = Library::RawCast(libs.At(i)); |
1646 | VTIR_Print("Lib '%s' at index %" Pd "\n" , lib.ToCString(), i); |
1647 | lib.set_index(i); |
1648 | } |
1649 | |
1650 | // Initialize library side table. |
1651 | library_infos_.SetLength(libs.Length()); |
1652 | for (intptr_t i = 0; i < libs.Length(); i++) { |
1653 | lib = Library::RawCast(libs.At(i)); |
1654 | // Mark the library dirty if it comes after the libraries we saved. |
1655 | library_infos_[i].dirty = |
1656 | i >= group_reload_context_->num_saved_libs_ || |
1657 | group_reload_context_->saved_libs_transitive_updated_->Contains( |
1658 | lib.index()); |
1659 | } |
1660 | } |
1661 | } |
1662 | |
1663 | void IsolateReloadContext::CommitAfterInstanceMorphing() { |
1664 | { |
1665 | const GrowableObjectArray& become_enum_mappings = |
1666 | GrowableObjectArray::Handle(become_enum_mappings_); |
1667 | UnorderedHashMap<BecomeMapTraits> become_map(become_map_storage_); |
1668 | intptr_t replacement_count = |
1669 | become_map.NumOccupied() + become_enum_mappings.Length() / 2; |
1670 | const Array& before = |
1671 | Array::Handle(Array::New(replacement_count, Heap::kOld)); |
1672 | const Array& after = |
1673 | Array::Handle(Array::New(replacement_count, Heap::kOld)); |
1674 | Object& obj = Object::Handle(); |
1675 | intptr_t replacement_index = 0; |
1676 | UnorderedHashMap<BecomeMapTraits>::Iterator it(&become_map); |
1677 | while (it.MoveNext()) { |
1678 | const intptr_t entry = it.Current(); |
1679 | obj = become_map.GetKey(entry); |
1680 | before.SetAt(replacement_index, obj); |
1681 | obj = become_map.GetPayload(entry, 0); |
1682 | after.SetAt(replacement_index, obj); |
1683 | replacement_index++; |
1684 | } |
1685 | for (intptr_t i = 0; i < become_enum_mappings.Length(); i += 2) { |
1686 | obj = become_enum_mappings.At(i); |
1687 | before.SetAt(replacement_index, obj); |
1688 | obj = become_enum_mappings.At(i + 1); |
1689 | after.SetAt(replacement_index, obj); |
1690 | replacement_index++; |
1691 | } |
1692 | ASSERT(replacement_index == replacement_count); |
1693 | become_map.Release(); |
1694 | |
1695 | Become::ElementsForwardIdentity(before, after); |
1696 | } |
1697 | |
1698 | // Rehash constants map for all classes. Constants are hashed by content, and |
1699 | // content may have changed from fields being added or removed. |
1700 | { |
1701 | TIMELINE_SCOPE(RehashConstants); |
1702 | I->RehashConstants(); |
1703 | } |
1704 | |
1705 | #ifdef DEBUG |
1706 | I->ValidateConstants(); |
1707 | #endif |
1708 | |
1709 | if (FLAG_identity_reload) { |
1710 | if (saved_num_cids_ != I->class_table()->NumCids()) { |
1711 | TIR_Print("Identity reload failed! B#C=%" Pd " A#C=%" Pd "\n" , |
1712 | saved_num_cids_, I->class_table()->NumCids()); |
1713 | } |
1714 | if (saved_num_tlc_cids_ != I->class_table()->NumTopLevelCids()) { |
1715 | TIR_Print("Identity reload failed! B#TLC=%" Pd " A#TLC=%" Pd "\n" , |
1716 | saved_num_tlc_cids_, I->class_table()->NumTopLevelCids()); |
1717 | } |
1718 | const auto& saved_libs = GrowableObjectArray::Handle(saved_libraries_); |
1719 | const GrowableObjectArray& libs = |
1720 | GrowableObjectArray::Handle(I->object_store()->libraries()); |
1721 | if (saved_libs.Length() != libs.Length()) { |
1722 | TIR_Print("Identity reload failed! B#L=%" Pd " A#L=%" Pd "\n" , |
1723 | saved_libs.Length(), libs.Length()); |
1724 | } |
1725 | } |
1726 | } |
1727 | |
1728 | bool IsolateReloadContext::IsDirty(const Library& lib) { |
1729 | const intptr_t index = lib.index(); |
1730 | if (index == static_cast<classid_t>(-1)) { |
1731 | // Treat deleted libraries as dirty. |
1732 | return true; |
1733 | } |
1734 | ASSERT((index >= 0) && (index < library_infos_.length())); |
1735 | return library_infos_[index].dirty; |
1736 | } |
1737 | |
1738 | void IsolateReloadContext::PostCommit() { |
1739 | TIMELINE_SCOPE(PostCommit); |
1740 | saved_root_library_ = Library::null(); |
1741 | saved_libraries_ = GrowableObjectArray::null(); |
1742 | InvalidateWorld(); |
1743 | } |
1744 | |
1745 | void IsolateGroupReloadContext::AddReasonForCancelling( |
1746 | ReasonForCancelling* reason) { |
1747 | reasons_to_cancel_reload_.Add(reason); |
1748 | } |
1749 | |
1750 | void IsolateGroupReloadContext::EnsureHasInstanceMorpherFor( |
1751 | classid_t cid, |
1752 | InstanceMorpher* instance_morpher) { |
1753 | for (intptr_t i = 0; i < instance_morphers_.length(); ++i) { |
1754 | if (instance_morphers_[i]->cid() == cid) { |
1755 | return; |
1756 | } |
1757 | } |
1758 | instance_morphers_.Add(instance_morpher); |
1759 | instance_morpher_by_cid_.Insert(instance_morpher); |
1760 | ASSERT(instance_morphers_[instance_morphers_.length() - 1]->cid() == cid); |
1761 | } |
1762 | |
1763 | void IsolateGroupReloadContext::ReportReasonsForCancelling() { |
1764 | ASSERT(FLAG_reload_force_rollback || HasReasonsForCancelling()); |
1765 | for (int i = 0; i < reasons_to_cancel_reload_.length(); i++) { |
1766 | reasons_to_cancel_reload_.At(i)->Report(this); |
1767 | } |
1768 | } |
1769 | |
1770 | void IsolateGroupReloadContext::MorphInstancesPhase1Allocate( |
1771 | ObjectLocator* locator, |
1772 | const Array& before, |
1773 | const Array& after) { |
1774 | ASSERT(HasInstanceMorphers()); |
1775 | |
1776 | if (FLAG_trace_reload) { |
1777 | LogBlock blocker; |
1778 | TIR_Print("MorphInstance: \n" ); |
1779 | for (intptr_t i = 0; i < instance_morphers_.length(); i++) { |
1780 | instance_morphers_.At(i)->Dump(); |
1781 | } |
1782 | } |
1783 | |
1784 | const intptr_t count = locator->count(); |
1785 | TIR_Print("Found %" Pd " object%s subject to morphing.\n" , count, |
1786 | (count > 1) ? "s" : "" ); |
1787 | |
1788 | for (intptr_t i = 0; i < instance_morphers_.length(); i++) { |
1789 | instance_morphers_.At(i)->CreateMorphedCopies(); |
1790 | } |
1791 | |
1792 | // Create the inputs for Become. |
1793 | intptr_t index = 0; |
1794 | for (intptr_t i = 0; i < instance_morphers_.length(); i++) { |
1795 | InstanceMorpher* morpher = instance_morphers_.At(i); |
1796 | for (intptr_t j = 0; j < morpher->before()->length(); j++) { |
1797 | before.SetAt(index, *morpher->before()->At(j)); |
1798 | after.SetAt(index, *morpher->after()->At(j)); |
1799 | index++; |
1800 | } |
1801 | } |
1802 | ASSERT(index == count); |
1803 | } |
1804 | |
1805 | void IsolateGroupReloadContext::MorphInstancesPhase2Become(const Array& before, |
1806 | const Array& after) { |
1807 | ASSERT(HasInstanceMorphers()); |
1808 | |
1809 | Become::ElementsForwardIdentity(before, after); |
1810 | // The heap now contains only instances with the new size. Ordinary GC is safe |
1811 | // again. |
1812 | } |
1813 | |
1814 | void IsolateGroupReloadContext::ForEachIsolate( |
1815 | std::function<void(Isolate*)> callback) { |
1816 | isolate_group_->ForEachIsolate(callback); |
1817 | } |
1818 | |
1819 | void IsolateReloadContext::ValidateReload() { |
1820 | TIMELINE_SCOPE(ValidateReload); |
1821 | |
1822 | TIR_Print("---- VALIDATING RELOAD\n" ); |
1823 | |
1824 | // Validate libraries. |
1825 | { |
1826 | ASSERT(library_map_storage_ != Array::null()); |
1827 | UnorderedHashMap<LibraryMapTraits> map(library_map_storage_); |
1828 | UnorderedHashMap<LibraryMapTraits>::Iterator it(&map); |
1829 | Library& lib = Library::Handle(); |
1830 | Library& new_lib = Library::Handle(); |
1831 | while (it.MoveNext()) { |
1832 | const intptr_t entry = it.Current(); |
1833 | new_lib = Library::RawCast(map.GetKey(entry)); |
1834 | lib = Library::RawCast(map.GetPayload(entry, 0)); |
1835 | if (new_lib.raw() != lib.raw()) { |
1836 | lib.CheckReload(new_lib, this); |
1837 | } |
1838 | } |
1839 | map.Release(); |
1840 | } |
1841 | |
1842 | // Validate classes. |
1843 | { |
1844 | ASSERT(class_map_storage_ != Array::null()); |
1845 | UnorderedHashMap<ClassMapTraits> map(class_map_storage_); |
1846 | UnorderedHashMap<ClassMapTraits>::Iterator it(&map); |
1847 | Class& cls = Class::Handle(); |
1848 | Class& new_cls = Class::Handle(); |
1849 | while (it.MoveNext()) { |
1850 | const intptr_t entry = it.Current(); |
1851 | new_cls = Class::RawCast(map.GetKey(entry)); |
1852 | cls = Class::RawCast(map.GetPayload(entry, 0)); |
1853 | if (new_cls.raw() != cls.raw()) { |
1854 | cls.CheckReload(new_cls, this); |
1855 | } |
1856 | } |
1857 | map.Release(); |
1858 | } |
1859 | } |
1860 | |
1861 | ClassPtr IsolateReloadContext::GetClassForHeapWalkAt(intptr_t cid) { |
1862 | ClassPtr* class_table = nullptr; |
1863 | intptr_t index = -1; |
1864 | if (ClassTable::IsTopLevelCid(cid)) { |
1865 | class_table = saved_tlc_class_table_.load(std::memory_order_acquire); |
1866 | index = ClassTable::IndexFromTopLevelCid(cid); |
1867 | ASSERT(index < saved_num_tlc_cids_); |
1868 | } else { |
1869 | class_table = saved_class_table_.load(std::memory_order_acquire); |
1870 | index = cid; |
1871 | ASSERT(cid > 0 && cid < saved_num_cids_); |
1872 | } |
1873 | if (class_table != nullptr) { |
1874 | return class_table[index]; |
1875 | } |
1876 | return isolate_->class_table()->At(cid); |
1877 | } |
1878 | |
1879 | intptr_t IsolateGroupReloadContext::GetClassSizeForHeapWalkAt(classid_t cid) { |
1880 | if (ClassTable::IsTopLevelCid(cid)) { |
1881 | return 0; |
1882 | } |
1883 | intptr_t* size_table = saved_size_table_.load(std::memory_order_acquire); |
1884 | if (size_table != nullptr) { |
1885 | ASSERT(cid < saved_num_cids_); |
1886 | return size_table[cid]; |
1887 | } else { |
1888 | return shared_class_table_->SizeAt(cid); |
1889 | } |
1890 | } |
1891 | |
1892 | void IsolateReloadContext::DiscardSavedClassTable(bool is_rollback) { |
1893 | ClassPtr* local_saved_class_table = |
1894 | saved_class_table_.load(std::memory_order_relaxed); |
1895 | ClassPtr* local_saved_tlc_class_table = |
1896 | saved_tlc_class_table_.load(std::memory_order_relaxed); |
1897 | I->class_table()->ResetAfterHotReload( |
1898 | local_saved_class_table, local_saved_tlc_class_table, saved_num_cids_, |
1899 | saved_num_tlc_cids_, is_rollback); |
1900 | saved_class_table_.store(nullptr, std::memory_order_release); |
1901 | saved_tlc_class_table_.store(nullptr, std::memory_order_release); |
1902 | } |
1903 | |
1904 | void IsolateGroupReloadContext::DiscardSavedClassTable(bool is_rollback) { |
1905 | intptr_t* local_saved_size_table = saved_size_table_; |
1906 | shared_class_table_->ResetAfterHotReload(local_saved_size_table, |
1907 | saved_num_cids_, is_rollback); |
1908 | saved_size_table_.store(nullptr, std::memory_order_release); |
1909 | } |
1910 | |
1911 | void IsolateGroupReloadContext::VisitObjectPointers( |
1912 | ObjectPointerVisitor* visitor) { |
1913 | visitor->VisitPointers(from(), to()); |
1914 | } |
1915 | |
1916 | void IsolateReloadContext::VisitObjectPointers(ObjectPointerVisitor* visitor) { |
1917 | visitor->VisitPointers(from(), to()); |
1918 | |
1919 | ClassPtr* saved_class_table = |
1920 | saved_class_table_.load(std::memory_order_relaxed); |
1921 | if (saved_class_table != NULL) { |
1922 | auto class_table = reinterpret_cast<ObjectPtr*>(&(saved_class_table[0])); |
1923 | visitor->VisitPointers(class_table, saved_num_cids_); |
1924 | } |
1925 | ClassPtr* saved_tlc_class_table = |
1926 | saved_class_table_.load(std::memory_order_relaxed); |
1927 | if (saved_tlc_class_table != NULL) { |
1928 | auto class_table = |
1929 | reinterpret_cast<ObjectPtr*>(&(saved_tlc_class_table[0])); |
1930 | visitor->VisitPointers(class_table, saved_num_tlc_cids_); |
1931 | } |
1932 | } |
1933 | |
1934 | ObjectStore* IsolateReloadContext::object_store() { |
1935 | return isolate_->object_store(); |
1936 | } |
1937 | |
1938 | void IsolateReloadContext::ResetUnoptimizedICsOnStack() { |
1939 | Thread* thread = Thread::Current(); |
1940 | StackZone stack_zone(thread); |
1941 | Zone* zone = stack_zone.GetZone(); |
1942 | |
1943 | Code& code = Code::Handle(zone); |
1944 | Bytecode& bytecode = Bytecode::Handle(zone); |
1945 | Function& function = Function::Handle(zone); |
1946 | CallSiteResetter resetter(zone); |
1947 | DartFrameIterator iterator(thread, |
1948 | StackFrameIterator::kNoCrossThreadIteration); |
1949 | StackFrame* frame = iterator.NextFrame(); |
1950 | while (frame != NULL) { |
1951 | if (frame->is_interpreted()) { |
1952 | bytecode = frame->LookupDartBytecode(); |
1953 | resetter.RebindStaticTargets(bytecode); |
1954 | } else { |
1955 | code = frame->LookupDartCode(); |
1956 | if (code.is_optimized() && !code.is_force_optimized()) { |
1957 | // If this code is optimized, we need to reset the ICs in the |
1958 | // corresponding unoptimized code, which will be executed when the stack |
1959 | // unwinds to the optimized code. |
1960 | function = code.function(); |
1961 | code = function.unoptimized_code(); |
1962 | ASSERT(!code.IsNull()); |
1963 | resetter.ResetSwitchableCalls(code); |
1964 | resetter.ResetCaches(code); |
1965 | } else { |
1966 | resetter.ResetSwitchableCalls(code); |
1967 | resetter.ResetCaches(code); |
1968 | } |
1969 | } |
1970 | frame = iterator.NextFrame(); |
1971 | } |
1972 | } |
1973 | |
1974 | void IsolateReloadContext::ResetMegamorphicCaches() { |
1975 | object_store()->set_megamorphic_cache_table(GrowableObjectArray::Handle()); |
1976 | // Since any current optimized code will not make any more calls, it may be |
1977 | // better to clear the table instead of clearing each of the caches, allow |
1978 | // the current megamorphic caches get GC'd and any new optimized code allocate |
1979 | // new ones. |
1980 | } |
1981 | |
1982 | class InvalidationCollector : public ObjectVisitor { |
1983 | public: |
1984 | InvalidationCollector(Zone* zone, |
1985 | GrowableArray<const Function*>* functions, |
1986 | GrowableArray<const KernelProgramInfo*>* kernel_infos, |
1987 | GrowableArray<const Field*>* fields, |
1988 | GrowableArray<const Instance*>* instances) |
1989 | : zone_(zone), |
1990 | functions_(functions), |
1991 | kernel_infos_(kernel_infos), |
1992 | fields_(fields), |
1993 | instances_(instances) {} |
1994 | virtual ~InvalidationCollector() {} |
1995 | |
1996 | void VisitObject(ObjectPtr obj) { |
1997 | intptr_t cid = obj->GetClassId(); |
1998 | if (cid == kFunctionCid) { |
1999 | const Function& func = |
2000 | Function::Handle(zone_, static_cast<FunctionPtr>(obj)); |
2001 | if (!func.ForceOptimize()) { |
2002 | // Force-optimized functions cannot deoptimize. |
2003 | functions_->Add(&func); |
2004 | } |
2005 | } else if (cid == kKernelProgramInfoCid) { |
2006 | kernel_infos_->Add(&KernelProgramInfo::Handle( |
2007 | zone_, static_cast<KernelProgramInfoPtr>(obj))); |
2008 | } else if (cid == kFieldCid) { |
2009 | fields_->Add(&Field::Handle(zone_, static_cast<FieldPtr>(obj))); |
2010 | } else if (cid > kNumPredefinedCids) { |
2011 | instances_->Add(&Instance::Handle(zone_, static_cast<InstancePtr>(obj))); |
2012 | } |
2013 | } |
2014 | |
2015 | private: |
2016 | Zone* const zone_; |
2017 | GrowableArray<const Function*>* const functions_; |
2018 | GrowableArray<const KernelProgramInfo*>* const kernel_infos_; |
2019 | GrowableArray<const Field*>* const fields_; |
2020 | GrowableArray<const Instance*>* const instances_; |
2021 | }; |
2022 | |
2023 | typedef UnorderedHashMap<SmiTraits> IntHashMap; |
2024 | |
2025 | void IsolateReloadContext::RunInvalidationVisitors() { |
2026 | TIR_Print("---- RUNNING INVALIDATION HEAP VISITORS\n" ); |
2027 | Thread* thread = Thread::Current(); |
2028 | StackZone stack_zone(thread); |
2029 | Zone* zone = stack_zone.GetZone(); |
2030 | |
2031 | Thread* mutator_thread = I->mutator_thread(); |
2032 | if (mutator_thread != nullptr) { |
2033 | Interpreter* interpreter = mutator_thread->interpreter(); |
2034 | if (interpreter != nullptr) { |
2035 | interpreter->ClearLookupCache(); |
2036 | } |
2037 | } |
2038 | |
2039 | GrowableArray<const Function*> functions(4 * KB); |
2040 | GrowableArray<const KernelProgramInfo*> kernel_infos(KB); |
2041 | GrowableArray<const Field*> fields(4 * KB); |
2042 | GrowableArray<const Instance*> instances(4 * KB); |
2043 | |
2044 | { |
2045 | HeapIterationScope iteration(thread); |
2046 | InvalidationCollector visitor(zone, &functions, &kernel_infos, &fields, |
2047 | &instances); |
2048 | iteration.IterateObjects(&visitor); |
2049 | } |
2050 | |
2051 | InvalidateKernelInfos(zone, kernel_infos); |
2052 | InvalidateFunctions(zone, functions); |
2053 | InvalidateFields(zone, fields, instances); |
2054 | } |
2055 | |
2056 | void IsolateReloadContext::InvalidateKernelInfos( |
2057 | Zone* zone, |
2058 | const GrowableArray<const KernelProgramInfo*>& kernel_infos) { |
2059 | TIMELINE_SCOPE(InvalidateKernelInfos); |
2060 | HANDLESCOPE(Thread::Current()); |
2061 | |
2062 | Array& data = Array::Handle(zone); |
2063 | Object& key = Object::Handle(zone); |
2064 | Smi& value = Smi::Handle(zone); |
2065 | for (intptr_t i = 0; i < kernel_infos.length(); i++) { |
2066 | const KernelProgramInfo& info = *kernel_infos[i]; |
2067 | // Clear the libraries cache. |
2068 | { |
2069 | data = info.libraries_cache(); |
2070 | ASSERT(!data.IsNull()); |
2071 | IntHashMap table(&key, &value, &data); |
2072 | table.Clear(); |
2073 | info.set_libraries_cache(table.Release()); |
2074 | } |
2075 | // Clear the classes cache. |
2076 | { |
2077 | data = info.classes_cache(); |
2078 | ASSERT(!data.IsNull()); |
2079 | IntHashMap table(&key, &value, &data); |
2080 | table.Clear(); |
2081 | info.set_classes_cache(table.Release()); |
2082 | } |
2083 | // Clear the bytecode object table. |
2084 | if (info.bytecode_component() != Array::null()) { |
2085 | kernel::BytecodeReader::ResetObjectTable(info); |
2086 | } |
2087 | } |
2088 | } |
2089 | |
2090 | void IsolateReloadContext::InvalidateFunctions( |
2091 | Zone* zone, |
2092 | const GrowableArray<const Function*>& functions) { |
2093 | TIMELINE_SCOPE(InvalidateFunctions); |
2094 | HANDLESCOPE(Thread::Current()); |
2095 | |
2096 | CallSiteResetter resetter(zone); |
2097 | |
2098 | Class& owning_class = Class::Handle(zone); |
2099 | Library& owning_lib = Library::Handle(zone); |
2100 | Code& code = Code::Handle(zone); |
2101 | Bytecode& bytecode = Bytecode::Handle(zone); |
2102 | for (intptr_t i = 0; i < functions.length(); i++) { |
2103 | const Function& func = *functions[i]; |
2104 | if (func.IsSignatureFunction()) { |
2105 | continue; |
2106 | } |
2107 | |
2108 | // Switch to unoptimized code or the lazy compilation stub. |
2109 | func.SwitchToLazyCompiledUnoptimizedCode(); |
2110 | |
2111 | // Grab the current code. |
2112 | code = func.CurrentCode(); |
2113 | ASSERT(!code.IsNull()); |
2114 | bytecode = func.bytecode(); |
2115 | |
2116 | owning_class = func.Owner(); |
2117 | owning_lib = owning_class.library(); |
2118 | const bool clear_code = IsDirty(owning_lib); |
2119 | const bool stub_code = code.IsStubCode(); |
2120 | |
2121 | // Zero edge counters, before clearing the ICDataArray, since that's where |
2122 | // they're held. |
2123 | resetter.ZeroEdgeCounters(func); |
2124 | |
2125 | if (!bytecode.IsNull()) { |
2126 | resetter.RebindStaticTargets(bytecode); |
2127 | } |
2128 | |
2129 | if (stub_code) { |
2130 | // Nothing to reset. |
2131 | } else if (clear_code) { |
2132 | VTIR_Print("Marking %s for recompilation, clearing code\n" , |
2133 | func.ToCString()); |
2134 | // Null out the ICData array and code. |
2135 | func.ClearICDataArray(); |
2136 | func.ClearCode(); |
2137 | func.SetWasCompiled(false); |
2138 | } else { |
2139 | // We are preserving the unoptimized code, reset instance calls and type |
2140 | // test caches. |
2141 | resetter.ResetSwitchableCalls(code); |
2142 | resetter.ResetCaches(code); |
2143 | } |
2144 | |
2145 | // Clear counters. |
2146 | func.set_usage_counter(0); |
2147 | func.set_deoptimization_counter(0); |
2148 | func.set_optimized_instruction_count(0); |
2149 | func.set_optimized_call_site_count(0); |
2150 | } |
2151 | } |
2152 | |
2153 | // Finds fields that are initialized or have a value that does not conform to |
2154 | // the field's static type, setting Field::needs_load_guard(). Accessors for |
2155 | // such fields are compiled with additional checks to handle lazy initialization |
2156 | // and to preserve type soundness. |
2157 | class FieldInvalidator { |
2158 | public: |
2159 | explicit FieldInvalidator(Zone* zone) |
2160 | : cls_(Class::Handle(zone)), |
2161 | cls_fields_(Array::Handle(zone)), |
2162 | entry_(Object::Handle(zone)), |
2163 | value_(Instance::Handle(zone)), |
2164 | type_(AbstractType::Handle(zone)), |
2165 | cache_(SubtypeTestCache::Handle(zone)), |
2166 | entries_(Array::Handle(zone)), |
2167 | instantiator_type_arguments_(TypeArguments::Handle(zone)), |
2168 | function_type_arguments_(TypeArguments::Handle(zone)), |
2169 | instance_cid_or_function_(Object::Handle(zone)), |
2170 | instance_type_arguments_(TypeArguments::Handle(zone)), |
2171 | parent_function_type_arguments_(TypeArguments::Handle(zone)), |
2172 | delayed_function_type_arguments_(TypeArguments::Handle(zone)) {} |
2173 | |
2174 | void CheckStatics(const GrowableArray<const Field*>& fields) { |
2175 | Thread* thread = Thread::Current(); |
2176 | Isolate* isolate = thread->isolate(); |
2177 | bool null_safety = isolate->null_safety(); |
2178 | HANDLESCOPE(thread); |
2179 | instantiator_type_arguments_ = TypeArguments::null(); |
2180 | for (intptr_t i = 0; i < fields.length(); i++) { |
2181 | const Field& field = *fields[i]; |
2182 | if (!field.is_static()) { |
2183 | continue; |
2184 | } |
2185 | if (field.needs_load_guard()) { |
2186 | continue; // Already guarding. |
2187 | } |
2188 | value_ = field.StaticValue(); |
2189 | if (value_.raw() != Object::sentinel().raw()) { |
2190 | CheckValueType(null_safety, value_, field); |
2191 | } |
2192 | } |
2193 | } |
2194 | |
2195 | void CheckInstances(const GrowableArray<const Instance*>& instances) { |
2196 | Thread* thread = Thread::Current(); |
2197 | Isolate* isolate = thread->isolate(); |
2198 | bool null_safety = isolate->null_safety(); |
2199 | HANDLESCOPE(thread); |
2200 | for (intptr_t i = 0; i < instances.length(); i++) { |
2201 | CheckInstance(null_safety, *instances[i]); |
2202 | } |
2203 | } |
2204 | |
2205 | private: |
2206 | DART_FORCE_INLINE |
2207 | void CheckInstance(bool null_safety, const Instance& instance) { |
2208 | cls_ = instance.clazz(); |
2209 | if (cls_.NumTypeArguments() > 0) { |
2210 | instantiator_type_arguments_ = instance.GetTypeArguments(); |
2211 | } else { |
2212 | instantiator_type_arguments_ = TypeArguments::null(); |
2213 | } |
2214 | cls_fields_ = cls_.OffsetToFieldMap(); |
2215 | for (intptr_t i = 0; i < cls_fields_.Length(); i++) { |
2216 | entry_ = cls_fields_.At(i); |
2217 | if (!entry_.IsField()) { |
2218 | continue; |
2219 | } |
2220 | const Field& field = Field::Cast(entry_); |
2221 | CheckInstanceField(null_safety, instance, field); |
2222 | } |
2223 | } |
2224 | |
2225 | DART_FORCE_INLINE |
2226 | void CheckInstanceField(bool null_safety, |
2227 | const Instance& instance, |
2228 | const Field& field) { |
2229 | if (field.needs_load_guard()) { |
2230 | return; // Already guarding. |
2231 | } |
2232 | value_ ^= instance.GetField(field); |
2233 | if (value_.raw() == Object::sentinel().raw()) { |
2234 | if (field.is_late()) { |
2235 | // Late fields already have lazy initialization logic. |
2236 | return; |
2237 | } |
2238 | // Needs guard for initialization. |
2239 | ASSERT(!FLAG_identity_reload); |
2240 | field.set_needs_load_guard(true); |
2241 | return; |
2242 | } |
2243 | CheckValueType(null_safety, value_, field); |
2244 | } |
2245 | |
2246 | DART_FORCE_INLINE |
2247 | void CheckValueType(bool null_safety, |
2248 | const Instance& value, |
2249 | const Field& field) { |
2250 | if (!null_safety && value.IsNull()) { |
2251 | return; |
2252 | } |
2253 | type_ = field.type(); |
2254 | if (type_.IsDynamicType()) { |
2255 | return; |
2256 | } |
2257 | |
2258 | cls_ = value.clazz(); |
2259 | const intptr_t cid = cls_.id(); |
2260 | if (cid == kClosureCid) { |
2261 | instance_cid_or_function_ = Closure::Cast(value).function(); |
2262 | instance_type_arguments_ = |
2263 | Closure::Cast(value).instantiator_type_arguments(); |
2264 | parent_function_type_arguments_ = |
2265 | Closure::Cast(value).function_type_arguments(); |
2266 | delayed_function_type_arguments_ = |
2267 | Closure::Cast(value).delayed_type_arguments(); |
2268 | } else { |
2269 | instance_cid_or_function_ = Smi::New(cid); |
2270 | if (cls_.NumTypeArguments() > 0) { |
2271 | instance_type_arguments_ = value_.GetTypeArguments(); |
2272 | } else { |
2273 | instance_type_arguments_ = TypeArguments::null(); |
2274 | } |
2275 | parent_function_type_arguments_ = TypeArguments::null(); |
2276 | delayed_function_type_arguments_ = TypeArguments::null(); |
2277 | } |
2278 | |
2279 | cache_ = field.type_test_cache(); |
2280 | if (cache_.IsNull()) { |
2281 | cache_ = SubtypeTestCache::New(); |
2282 | field.set_type_test_cache(cache_); |
2283 | } |
2284 | entries_ = cache_.cache(); |
2285 | |
2286 | bool cache_hit = false; |
2287 | for (intptr_t i = 0; entries_.At(i) != Object::null(); |
2288 | i += SubtypeTestCache::kTestEntryLength) { |
2289 | if ((entries_.At(i + SubtypeTestCache::kInstanceClassIdOrFunction) == |
2290 | instance_cid_or_function_.raw()) && |
2291 | (entries_.At(i + SubtypeTestCache::kInstanceTypeArguments) == |
2292 | instance_type_arguments_.raw()) && |
2293 | (entries_.At(i + SubtypeTestCache::kInstantiatorTypeArguments) == |
2294 | instantiator_type_arguments_.raw()) && |
2295 | (entries_.At(i + SubtypeTestCache::kFunctionTypeArguments) == |
2296 | function_type_arguments_.raw()) && |
2297 | (entries_.At( |
2298 | i + SubtypeTestCache::kInstanceParentFunctionTypeArguments) == |
2299 | parent_function_type_arguments_.raw()) && |
2300 | (entries_.At( |
2301 | i + SubtypeTestCache::kInstanceDelayedFunctionTypeArguments) == |
2302 | delayed_function_type_arguments_.raw())) { |
2303 | cache_hit = true; |
2304 | if (entries_.At(i + SubtypeTestCache::kTestResult) != |
2305 | Bool::True().raw()) { |
2306 | ASSERT(!FLAG_identity_reload); |
2307 | field.set_needs_load_guard(true); |
2308 | } |
2309 | break; |
2310 | } |
2311 | } |
2312 | |
2313 | if (!cache_hit) { |
2314 | if (!value.IsAssignableTo(type_, instantiator_type_arguments_, |
2315 | function_type_arguments_)) { |
2316 | ASSERT(!FLAG_identity_reload); |
2317 | field.set_needs_load_guard(true); |
2318 | } else { |
2319 | cache_.AddCheck(instance_cid_or_function_, instance_type_arguments_, |
2320 | instantiator_type_arguments_, function_type_arguments_, |
2321 | parent_function_type_arguments_, |
2322 | delayed_function_type_arguments_, Bool::True()); |
2323 | } |
2324 | } |
2325 | } |
2326 | |
2327 | Class& cls_; |
2328 | Array& cls_fields_; |
2329 | Object& entry_; |
2330 | Instance& value_; |
2331 | AbstractType& type_; |
2332 | SubtypeTestCache& cache_; |
2333 | Array& entries_; |
2334 | TypeArguments& instantiator_type_arguments_; |
2335 | TypeArguments& function_type_arguments_; |
2336 | Object& instance_cid_or_function_; |
2337 | TypeArguments& instance_type_arguments_; |
2338 | TypeArguments& parent_function_type_arguments_; |
2339 | TypeArguments& delayed_function_type_arguments_; |
2340 | }; |
2341 | |
2342 | void IsolateReloadContext::InvalidateFields( |
2343 | Zone* zone, |
2344 | const GrowableArray<const Field*>& fields, |
2345 | const GrowableArray<const Instance*>& instances) { |
2346 | TIMELINE_SCOPE(InvalidateFields); |
2347 | SafepointMutexLocker ml(isolate()->group()->subtype_test_cache_mutex()); |
2348 | FieldInvalidator invalidator(zone); |
2349 | invalidator.CheckStatics(fields); |
2350 | invalidator.CheckInstances(instances); |
2351 | } |
2352 | |
2353 | void IsolateReloadContext::InvalidateWorld() { |
2354 | TIMELINE_SCOPE(InvalidateWorld); |
2355 | TIR_Print("---- INVALIDATING WORLD\n" ); |
2356 | ResetMegamorphicCaches(); |
2357 | if (FLAG_trace_deoptimization) { |
2358 | THR_Print("Deopt for reload\n" ); |
2359 | } |
2360 | DeoptimizeFunctionsOnStack(); |
2361 | ResetUnoptimizedICsOnStack(); |
2362 | RunInvalidationVisitors(); |
2363 | } |
2364 | |
2365 | ClassPtr IsolateReloadContext::OldClassOrNull(const Class& replacement_or_new) { |
2366 | UnorderedHashSet<ClassMapTraits> old_classes_set(old_classes_set_storage_); |
2367 | Class& cls = Class::Handle(); |
2368 | cls ^= old_classes_set.GetOrNull(replacement_or_new); |
2369 | old_classes_set_storage_ = old_classes_set.Release().raw(); |
2370 | return cls.raw(); |
2371 | } |
2372 | |
2373 | StringPtr IsolateReloadContext::FindLibraryPrivateKey( |
2374 | const Library& replacement_or_new) { |
2375 | const Library& old = Library::Handle(OldLibraryOrNull(replacement_or_new)); |
2376 | if (old.IsNull()) { |
2377 | return String::null(); |
2378 | } |
2379 | #if defined(DEBUG) |
2380 | VTIR_Print("`%s` is getting `%s`'s private key.\n" , |
2381 | String::Handle(replacement_or_new.url()).ToCString(), |
2382 | String::Handle(old.url()).ToCString()); |
2383 | #endif |
2384 | return old.private_key(); |
2385 | } |
2386 | |
2387 | LibraryPtr IsolateReloadContext::OldLibraryOrNull( |
2388 | const Library& replacement_or_new) { |
2389 | UnorderedHashSet<LibraryMapTraits> old_libraries_set( |
2390 | old_libraries_set_storage_); |
2391 | Library& lib = Library::Handle(); |
2392 | lib ^= old_libraries_set.GetOrNull(replacement_or_new); |
2393 | old_libraries_set.Release(); |
2394 | |
2395 | if (lib.IsNull() && |
2396 | (group_reload_context_->root_url_prefix_ != String::null()) && |
2397 | (group_reload_context_->old_root_url_prefix_ != String::null())) { |
2398 | return OldLibraryOrNullBaseMoved(replacement_or_new); |
2399 | } |
2400 | return lib.raw(); |
2401 | } |
2402 | |
2403 | // Attempt to find the pair to |replacement_or_new| with the knowledge that |
2404 | // the base url prefix has moved. |
2405 | LibraryPtr IsolateReloadContext::OldLibraryOrNullBaseMoved( |
2406 | const Library& replacement_or_new) { |
2407 | const String& url_prefix = |
2408 | String::Handle(group_reload_context_->root_url_prefix_); |
2409 | const String& old_url_prefix = |
2410 | String::Handle(group_reload_context_->old_root_url_prefix_); |
2411 | const intptr_t prefix_length = url_prefix.Length(); |
2412 | const intptr_t old_prefix_length = old_url_prefix.Length(); |
2413 | const String& new_url = String::Handle(replacement_or_new.url()); |
2414 | const String& suffix = |
2415 | String::Handle(String::SubString(new_url, prefix_length)); |
2416 | if (!new_url.StartsWith(url_prefix)) { |
2417 | return Library::null(); |
2418 | } |
2419 | Library& old = Library::Handle(); |
2420 | String& old_url = String::Handle(); |
2421 | String& old_suffix = String::Handle(); |
2422 | const auto& saved_libs = GrowableObjectArray::Handle(saved_libraries_); |
2423 | ASSERT(!saved_libs.IsNull()); |
2424 | for (intptr_t i = 0; i < saved_libs.Length(); i++) { |
2425 | old = Library::RawCast(saved_libs.At(i)); |
2426 | old_url = old.url(); |
2427 | if (!old_url.StartsWith(old_url_prefix)) { |
2428 | continue; |
2429 | } |
2430 | old_suffix = String::SubString(old_url, old_prefix_length); |
2431 | if (old_suffix.IsNull()) { |
2432 | continue; |
2433 | } |
2434 | if (old_suffix.Equals(suffix)) { |
2435 | TIR_Print("`%s` is moving to `%s`\n" , old_url.ToCString(), |
2436 | new_url.ToCString()); |
2437 | return old.raw(); |
2438 | } |
2439 | } |
2440 | return Library::null(); |
2441 | } |
2442 | |
2443 | void IsolateReloadContext::BuildLibraryMapping() { |
2444 | const GrowableObjectArray& libs = |
2445 | GrowableObjectArray::Handle(object_store()->libraries()); |
2446 | |
2447 | Library& replacement_or_new = Library::Handle(); |
2448 | Library& old = Library::Handle(); |
2449 | for (intptr_t i = group_reload_context_->num_saved_libs_; i < libs.Length(); |
2450 | i++) { |
2451 | replacement_or_new = Library::RawCast(libs.At(i)); |
2452 | old = OldLibraryOrNull(replacement_or_new); |
2453 | if (old.IsNull()) { |
2454 | if (FLAG_identity_reload) { |
2455 | TIR_Print("Could not find original library for %s\n" , |
2456 | replacement_or_new.ToCString()); |
2457 | UNREACHABLE(); |
2458 | } |
2459 | // New library. |
2460 | AddLibraryMapping(replacement_or_new, replacement_or_new); |
2461 | } else { |
2462 | ASSERT(!replacement_or_new.is_dart_scheme()); |
2463 | // Replaced class. |
2464 | AddLibraryMapping(replacement_or_new, old); |
2465 | |
2466 | AddBecomeMapping(old, replacement_or_new); |
2467 | } |
2468 | } |
2469 | } |
2470 | |
2471 | // Find classes that have been removed from the program. |
2472 | // Instances of these classes may still be referenced from variables, so the |
2473 | // functions of these class may still execute in the future, and they need to |
2474 | // be given patch class owners still they correctly reference their (old) kernel |
2475 | // data even after the library's kernel data is updated. |
2476 | // |
2477 | // Note that all such classes must belong to a library that has either been |
2478 | // changed or removed. |
2479 | void IsolateReloadContext::BuildRemovedClassesSet() { |
2480 | // Find all old classes [mapped_old_classes_set]. |
2481 | UnorderedHashMap<ClassMapTraits> class_map(class_map_storage_); |
2482 | UnorderedHashSet<ClassMapTraits> mapped_old_classes_set( |
2483 | HashTables::New<UnorderedHashSet<ClassMapTraits> >( |
2484 | class_map.NumOccupied())); |
2485 | { |
2486 | UnorderedHashMap<ClassMapTraits>::Iterator it(&class_map); |
2487 | Class& cls = Class::Handle(); |
2488 | Class& new_cls = Class::Handle(); |
2489 | while (it.MoveNext()) { |
2490 | const intptr_t entry = it.Current(); |
2491 | new_cls = Class::RawCast(class_map.GetKey(entry)); |
2492 | cls = Class::RawCast(class_map.GetPayload(entry, 0)); |
2493 | mapped_old_classes_set.InsertOrGet(cls); |
2494 | } |
2495 | } |
2496 | class_map.Release(); |
2497 | |
2498 | // Find all reloaded libraries [mapped_old_library_set]. |
2499 | UnorderedHashMap<LibraryMapTraits> library_map(library_map_storage_); |
2500 | UnorderedHashMap<LibraryMapTraits>::Iterator it_library(&library_map); |
2501 | UnorderedHashSet<LibraryMapTraits> mapped_old_library_set( |
2502 | HashTables::New<UnorderedHashSet<LibraryMapTraits> >( |
2503 | library_map.NumOccupied())); |
2504 | { |
2505 | Library& old_library = Library::Handle(); |
2506 | Library& new_library = Library::Handle(); |
2507 | while (it_library.MoveNext()) { |
2508 | const intptr_t entry = it_library.Current(); |
2509 | new_library ^= library_map.GetKey(entry); |
2510 | old_library ^= library_map.GetPayload(entry, 0); |
2511 | if (new_library.raw() != old_library.raw()) { |
2512 | mapped_old_library_set.InsertOrGet(old_library); |
2513 | } |
2514 | } |
2515 | } |
2516 | |
2517 | // For every old class, check if it's library was reloaded and if |
2518 | // the class was mapped. If the class wasn't mapped - add it to |
2519 | // [removed_class_set]. |
2520 | UnorderedHashSet<ClassMapTraits> old_classes_set(old_classes_set_storage_); |
2521 | UnorderedHashSet<ClassMapTraits>::Iterator it(&old_classes_set); |
2522 | UnorderedHashSet<ClassMapTraits> removed_class_set( |
2523 | removed_class_set_storage_); |
2524 | Class& old_cls = Class::Handle(); |
2525 | Class& new_cls = Class::Handle(); |
2526 | Library& old_library = Library::Handle(); |
2527 | Library& mapped_old_library = Library::Handle(); |
2528 | while (it.MoveNext()) { |
2529 | const intptr_t entry = it.Current(); |
2530 | old_cls ^= Class::RawCast(old_classes_set.GetKey(entry)); |
2531 | old_library = old_cls.library(); |
2532 | if (old_library.IsNull()) { |
2533 | continue; |
2534 | } |
2535 | mapped_old_library ^= mapped_old_library_set.GetOrNull(old_library); |
2536 | if (!mapped_old_library.IsNull()) { |
2537 | new_cls ^= mapped_old_classes_set.GetOrNull(old_cls); |
2538 | if (new_cls.IsNull()) { |
2539 | removed_class_set.InsertOrGet(old_cls); |
2540 | } |
2541 | } |
2542 | } |
2543 | removed_class_set_storage_ = removed_class_set.Release().raw(); |
2544 | |
2545 | old_classes_set.Release(); |
2546 | mapped_old_classes_set.Release(); |
2547 | mapped_old_library_set.Release(); |
2548 | library_map.Release(); |
2549 | } |
2550 | |
2551 | void IsolateReloadContext::AddClassMapping(const Class& replacement_or_new, |
2552 | const Class& original) { |
2553 | UnorderedHashMap<ClassMapTraits> map(class_map_storage_); |
2554 | bool update = map.UpdateOrInsert(replacement_or_new, original); |
2555 | ASSERT(!update); |
2556 | // The storage given to the map may have been reallocated, remember the new |
2557 | // address. |
2558 | class_map_storage_ = map.Release().raw(); |
2559 | } |
2560 | |
2561 | void IsolateReloadContext::AddLibraryMapping(const Library& replacement_or_new, |
2562 | const Library& original) { |
2563 | UnorderedHashMap<LibraryMapTraits> map(library_map_storage_); |
2564 | bool update = map.UpdateOrInsert(replacement_or_new, original); |
2565 | ASSERT(!update); |
2566 | // The storage given to the map may have been reallocated, remember the new |
2567 | // address. |
2568 | library_map_storage_ = map.Release().raw(); |
2569 | } |
2570 | |
2571 | void IsolateReloadContext::AddStaticFieldMapping(const Field& old_field, |
2572 | const Field& new_field) { |
2573 | ASSERT(old_field.is_static()); |
2574 | ASSERT(new_field.is_static()); |
2575 | |
2576 | AddBecomeMapping(old_field, new_field); |
2577 | } |
2578 | |
2579 | void IsolateReloadContext::AddBecomeMapping(const Object& old, |
2580 | const Object& neu) { |
2581 | ASSERT(become_map_storage_ != Array::null()); |
2582 | UnorderedHashMap<BecomeMapTraits> become_map(become_map_storage_); |
2583 | bool update = become_map.UpdateOrInsert(old, neu); |
2584 | ASSERT(!update); |
2585 | become_map_storage_ = become_map.Release().raw(); |
2586 | } |
2587 | |
2588 | void IsolateReloadContext::AddEnumBecomeMapping(const Object& old, |
2589 | const Object& neu) { |
2590 | const GrowableObjectArray& become_enum_mappings = |
2591 | GrowableObjectArray::Handle(become_enum_mappings_); |
2592 | become_enum_mappings.Add(old); |
2593 | become_enum_mappings.Add(neu); |
2594 | ASSERT((become_enum_mappings.Length() % 2) == 0); |
2595 | } |
2596 | |
2597 | void IsolateReloadContext::RebuildDirectSubclasses() { |
2598 | ClassTable* class_table = I->class_table(); |
2599 | intptr_t num_cids = class_table->NumCids(); |
2600 | |
2601 | // Clear the direct subclasses for all classes. |
2602 | Class& cls = Class::Handle(); |
2603 | GrowableObjectArray& subclasses = GrowableObjectArray::Handle(); |
2604 | for (intptr_t i = 1; i < num_cids; i++) { |
2605 | if (class_table->HasValidClassAt(i)) { |
2606 | cls = class_table->At(i); |
2607 | if (!cls.is_declaration_loaded()) { |
2608 | continue; // Can't have any subclasses or implementors yet. |
2609 | } |
2610 | subclasses = cls.direct_subclasses(); |
2611 | if (!subclasses.IsNull()) { |
2612 | cls.ClearDirectSubclasses(); |
2613 | } |
2614 | subclasses = cls.direct_implementors(); |
2615 | if (!subclasses.IsNull()) { |
2616 | cls.ClearDirectImplementors(); |
2617 | } |
2618 | } |
2619 | } |
2620 | |
2621 | // Recompute the direct subclasses / implementors. |
2622 | |
2623 | AbstractType& super_type = AbstractType::Handle(); |
2624 | Class& super_cls = Class::Handle(); |
2625 | |
2626 | Array& interface_types = Array::Handle(); |
2627 | AbstractType& interface_type = AbstractType::Handle(); |
2628 | Class& interface_class = Class::Handle(); |
2629 | |
2630 | for (intptr_t i = 1; i < num_cids; i++) { |
2631 | if (class_table->HasValidClassAt(i)) { |
2632 | cls = class_table->At(i); |
2633 | if (!cls.is_declaration_loaded()) { |
2634 | continue; // Will register itself later when loaded. |
2635 | } |
2636 | super_type = cls.super_type(); |
2637 | if (!super_type.IsNull() && !super_type.IsObjectType()) { |
2638 | super_cls = cls.SuperClass(); |
2639 | ASSERT(!super_cls.IsNull()); |
2640 | super_cls.AddDirectSubclass(cls); |
2641 | } |
2642 | |
2643 | interface_types = cls.interfaces(); |
2644 | if (!interface_types.IsNull()) { |
2645 | const intptr_t mixin_index = cls.is_transformed_mixin_application() |
2646 | ? interface_types.Length() - 1 |
2647 | : -1; |
2648 | for (intptr_t j = 0; j < interface_types.Length(); ++j) { |
2649 | interface_type ^= interface_types.At(j); |
2650 | interface_class = interface_type.type_class(); |
2651 | interface_class.AddDirectImplementor( |
2652 | cls, /* is_mixin = */ i == mixin_index); |
2653 | } |
2654 | } |
2655 | } |
2656 | } |
2657 | } |
2658 | |
2659 | #endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME) |
2660 | |
2661 | } // namespace dart |
2662 | |