| 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 | #ifndef RUNTIME_VM_ISOLATE_RELOAD_H_ |
| 6 | #define RUNTIME_VM_ISOLATE_RELOAD_H_ |
| 7 | |
| 8 | #include <functional> |
| 9 | #include <memory> |
| 10 | |
| 11 | #include "include/dart_tools_api.h" |
| 12 | |
| 13 | #include "vm/compiler/jit/compiler.h" |
| 14 | #include "vm/globals.h" |
| 15 | #include "vm/growable_array.h" |
| 16 | #include "vm/hash_map.h" |
| 17 | #include "vm/heap/become.h" |
| 18 | #include "vm/heap/safepoint.h" |
| 19 | #include "vm/log.h" |
| 20 | #include "vm/object.h" |
| 21 | |
| 22 | DECLARE_FLAG(bool, trace_reload); |
| 23 | DECLARE_FLAG(bool, trace_reload_verbose); |
| 24 | |
| 25 | // 'Trace Isolate Reload' TIR_Print |
| 26 | #if defined(_MSC_VER) |
| 27 | #define TIR_Print(format, ...) \ |
| 28 | if (FLAG_trace_reload) Log::Current()->Print(format, __VA_ARGS__) |
| 29 | #else |
| 30 | #define TIR_Print(format, ...) \ |
| 31 | if (FLAG_trace_reload) Log::Current()->Print(format, ##__VA_ARGS__) |
| 32 | #endif |
| 33 | |
| 34 | // 'Verbose Trace Isolate Reload' VTIR_Print |
| 35 | #if defined(_MSC_VER) |
| 36 | #define VTIR_Print(format, ...) \ |
| 37 | if (FLAG_trace_reload_verbose) Log::Current()->Print(format, __VA_ARGS__) |
| 38 | #else |
| 39 | #define VTIR_Print(format, ...) \ |
| 40 | if (FLAG_trace_reload_verbose) Log::Current()->Print(format, ##__VA_ARGS__) |
| 41 | #endif |
| 42 | |
| 43 | #if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME) |
| 44 | |
| 45 | namespace dart { |
| 46 | |
| 47 | class BitVector; |
| 48 | class GrowableObjectArray; |
| 49 | class Isolate; |
| 50 | class Library; |
| 51 | class ObjectLocator; |
| 52 | class ObjectPointerVisitor; |
| 53 | class ObjectStore; |
| 54 | class Script; |
| 55 | class UpdateClassesVisitor; |
| 56 | |
| 57 | struct FieldMapping { |
| 58 | intptr_t offset; |
| 59 | intptr_t box_cid; // kIllegalCid if field is boxed |
| 60 | }; |
| 61 | |
| 62 | using FieldMappingArray = ZoneGrowableArray<FieldMapping>; |
| 63 | using FieldOffsetArray = ZoneGrowableArray<intptr_t>; |
| 64 | |
| 65 | class InstanceMorpher : public ZoneAllocated { |
| 66 | public: |
| 67 | // Creates a new [InstanceMorpher] based on the [from]/[to] class |
| 68 | // descriptions. |
| 69 | static InstanceMorpher* CreateFromClassDescriptors(Zone* zone, |
| 70 | ClassTable* class_table, |
| 71 | const Class& from, |
| 72 | const Class& to); |
| 73 | |
| 74 | InstanceMorpher(Zone* zone, |
| 75 | classid_t cid, |
| 76 | const Class& old_class, |
| 77 | const Class& new_class, |
| 78 | FieldMappingArray* mapping, |
| 79 | FieldOffsetArray* new_fields_offsets); |
| 80 | virtual ~InstanceMorpher() {} |
| 81 | |
| 82 | // Adds an object to be morphed. |
| 83 | void AddObject(ObjectPtr object); |
| 84 | |
| 85 | // Create the morphed objects based on the before() list. |
| 86 | void CreateMorphedCopies(Become* become); |
| 87 | |
| 88 | // Append the morper info to JSON array. |
| 89 | void AppendTo(JSONArray* array); |
| 90 | |
| 91 | // Returns the cid associated with the from_ and to_ class. |
| 92 | intptr_t cid() const { return cid_; } |
| 93 | |
| 94 | // Dumps the field mappings for the [cid()] class. |
| 95 | void Dump() const; |
| 96 | |
| 97 | private: |
| 98 | Zone* zone_; |
| 99 | classid_t cid_; |
| 100 | const Class& old_class_; |
| 101 | const Class& new_class_; |
| 102 | FieldMappingArray* mapping_; |
| 103 | FieldOffsetArray* new_fields_offsets_; |
| 104 | |
| 105 | GrowableArray<const Instance*> before_; |
| 106 | }; |
| 107 | |
| 108 | class ReasonForCancelling : public ZoneAllocated { |
| 109 | public: |
| 110 | explicit ReasonForCancelling(Zone* zone) {} |
| 111 | virtual ~ReasonForCancelling() {} |
| 112 | |
| 113 | // Reports a reason for cancelling reload. |
| 114 | void Report(IsolateGroupReloadContext* context); |
| 115 | |
| 116 | // Conversion to a VM error object. |
| 117 | // Default implementation calls ToString. |
| 118 | virtual ErrorPtr ToError(); |
| 119 | |
| 120 | // Conversion to a string object. |
| 121 | // Default implementation calls ToError. |
| 122 | virtual StringPtr ToString(); |
| 123 | |
| 124 | // Append the reason to JSON array. |
| 125 | virtual void AppendTo(JSONArray* array); |
| 126 | |
| 127 | // Concrete subclasses must override either ToError or ToString. |
| 128 | }; |
| 129 | |
| 130 | // Abstract class for also capturing the from_ and to_ class. |
| 131 | class ClassReasonForCancelling : public ReasonForCancelling { |
| 132 | public: |
| 133 | ClassReasonForCancelling(Zone* zone, const Class& from, const Class& to); |
| 134 | void AppendTo(JSONArray* array); |
| 135 | |
| 136 | protected: |
| 137 | const Class& from_; |
| 138 | const Class& to_; |
| 139 | }; |
| 140 | |
| 141 | class IsolateGroupReloadContext { |
| 142 | public: |
| 143 | IsolateGroupReloadContext(IsolateGroup* isolate, |
| 144 | ClassTable* class_table, |
| 145 | JSONStream* js); |
| 146 | ~IsolateGroupReloadContext(); |
| 147 | |
| 148 | // If kernel_buffer is provided, the VM takes ownership when Reload is called. |
| 149 | bool Reload(bool force_reload, |
| 150 | const char* root_script_url = nullptr, |
| 151 | const char* packages_url = nullptr, |
| 152 | const uint8_t* kernel_buffer = nullptr, |
| 153 | intptr_t kernel_buffer_size = 0); |
| 154 | |
| 155 | // All zone allocated objects must be allocated from this zone. |
| 156 | Zone* zone() const { return zone_; } |
| 157 | |
| 158 | IsolateGroup* isolate_group() const { return isolate_group_; } |
| 159 | bool reload_aborted() const { return HasReasonsForCancelling(); } |
| 160 | bool reload_skipped() const { return reload_skipped_; } |
| 161 | ErrorPtr error() const; |
| 162 | int64_t start_time_micros() const { return start_time_micros_; } |
| 163 | int64_t reload_timestamp() const { return reload_timestamp_; } |
| 164 | |
| 165 | static Dart_FileModifiedCallback file_modified_callback() { |
| 166 | return file_modified_callback_; |
| 167 | } |
| 168 | static void SetFileModifiedCallback(Dart_FileModifiedCallback callback) { |
| 169 | file_modified_callback_ = callback; |
| 170 | } |
| 171 | |
| 172 | private: |
| 173 | // Tells whether there are reasons for cancelling the reload. |
| 174 | bool HasReasonsForCancelling() const { |
| 175 | return !reasons_to_cancel_reload_.is_empty(); |
| 176 | } |
| 177 | |
| 178 | // Record problem for this reload. |
| 179 | void AddReasonForCancelling(ReasonForCancelling* reason); |
| 180 | |
| 181 | // Reports all reasons for cancelling reload. |
| 182 | void ReportReasonsForCancelling(); |
| 183 | |
| 184 | // Reports the details of a reload operation. |
| 185 | void ReportOnJSON(JSONStream* stream, intptr_t final_library_count); |
| 186 | |
| 187 | // Ensures there is a instance morpher for [cid], if not it will use |
| 188 | // [instance_morpher] |
| 189 | void EnsureHasInstanceMorpherFor(classid_t cid, |
| 190 | InstanceMorpher* instance_morpher); |
| 191 | |
| 192 | // Tells whether instance in the heap must be morphed. |
| 193 | bool HasInstanceMorphers() const { return !instance_morphers_.is_empty(); } |
| 194 | |
| 195 | // Called by both FinalizeLoading and FinalizeFailedLoad. |
| 196 | void CommonFinalizeTail(intptr_t final_library_count); |
| 197 | |
| 198 | // Report back through the observatory channels. |
| 199 | void ReportError(const Error& error); |
| 200 | void ReportSuccess(); |
| 201 | |
| 202 | void VisitObjectPointers(ObjectPointerVisitor* visitor); |
| 203 | |
| 204 | void GetRootLibUrl(const char* root_script_url); |
| 205 | char* CompileToKernel(bool force_reload, |
| 206 | const char* packages_url, |
| 207 | const uint8_t** kernel_buffer, |
| 208 | intptr_t* kernel_buffer_size); |
| 209 | void BuildModifiedLibrariesClosure(BitVector* modified_libs); |
| 210 | void FindModifiedSources(bool force_reload, |
| 211 | Dart_SourceFile** modified_sources, |
| 212 | intptr_t* count, |
| 213 | const char* packages_url); |
| 214 | bool ScriptModifiedSince(const Script& script, int64_t since); |
| 215 | |
| 216 | void MorphInstancesPhase1Allocate(ObjectLocator* locator, Become* become); |
| 217 | void MorphInstancesPhase2Become(Become* become); |
| 218 | |
| 219 | void ForEachIsolate(std::function<void(Isolate*)> callback); |
| 220 | |
| 221 | // The zone used for all reload related allocations. |
| 222 | Zone* zone_; |
| 223 | |
| 224 | IsolateGroup* isolate_group_; |
| 225 | ClassTable* class_table_; |
| 226 | |
| 227 | int64_t start_time_micros_ = -1; |
| 228 | int64_t reload_timestamp_ = -1; |
| 229 | bool reload_skipped_ = false; |
| 230 | bool reload_finalized_ = false; |
| 231 | JSONStream* js_; |
| 232 | intptr_t num_old_libs_ = -1; |
| 233 | |
| 234 | intptr_t num_received_libs_ = -1; |
| 235 | intptr_t bytes_received_libs_ = -1; |
| 236 | intptr_t num_received_classes_ = -1; |
| 237 | intptr_t num_received_procedures_ = -1; |
| 238 | intptr_t num_saved_libs_ = -1; |
| 239 | |
| 240 | // Required trait for the instance_morpher_by_cid_; |
| 241 | struct MorpherTrait { |
| 242 | typedef InstanceMorpher* Value; |
| 243 | typedef intptr_t Key; |
| 244 | typedef InstanceMorpher* Pair; |
| 245 | |
| 246 | static Key KeyOf(Pair kv) { return kv->cid(); } |
| 247 | static Value ValueOf(Pair kv) { return kv; } |
| 248 | static uword Hash(Key key) { return Utils::WordHash(key); } |
| 249 | static bool IsKeyEqual(Pair kv, Key key) { return kv->cid() == key; } |
| 250 | }; |
| 251 | |
| 252 | // Collect the necessary instance transformation for schema changes. |
| 253 | GrowableArray<InstanceMorpher*> instance_morphers_; |
| 254 | |
| 255 | // Collects the reasons for cancelling the reload. |
| 256 | GrowableArray<ReasonForCancelling*> reasons_to_cancel_reload_; |
| 257 | |
| 258 | // Hash map from cid to InstanceMorpher. |
| 259 | DirectChainedHashMap<MorpherTrait> instance_morpher_by_cid_; |
| 260 | |
| 261 | // A bit vector indicating which of the original libraries were modified. |
| 262 | BitVector* modified_libs_ = nullptr; |
| 263 | |
| 264 | // A bit vector indicating which of the original libraries were modified, |
| 265 | // or where a transitive dependency was modified. |
| 266 | BitVector* modified_libs_transitive_ = nullptr; |
| 267 | |
| 268 | // A bit vector indicating which of the saved libraries that transitively |
| 269 | // depend on a modified library. |
| 270 | BitVector* saved_libs_transitive_updated_ = nullptr; |
| 271 | |
| 272 | String& root_lib_url_; |
| 273 | ObjectPtr* from() { return reinterpret_cast<ObjectPtr*>(&root_url_prefix_); } |
| 274 | StringPtr root_url_prefix_; |
| 275 | StringPtr old_root_url_prefix_; |
| 276 | ObjectPtr* to() { |
| 277 | return reinterpret_cast<ObjectPtr*>(&old_root_url_prefix_); |
| 278 | } |
| 279 | |
| 280 | friend class Isolate; |
| 281 | friend class Class; // AddStaticFieldMapping, AddEnumBecomeMapping. |
| 282 | friend class Library; |
| 283 | friend class ObjectLocator; |
| 284 | friend class ReasonForCancelling; |
| 285 | friend class ProgramReloadContext; |
| 286 | friend class IsolateGroup; |
| 287 | |
| 288 | static Dart_FileModifiedCallback file_modified_callback_; |
| 289 | }; |
| 290 | |
| 291 | class ProgramReloadContext { |
| 292 | public: |
| 293 | ProgramReloadContext( |
| 294 | std::shared_ptr<IsolateGroupReloadContext> group_reload_context, |
| 295 | IsolateGroup* isolate_group); |
| 296 | ~ProgramReloadContext(); |
| 297 | |
| 298 | // All zone allocated objects must be allocated from this zone. |
| 299 | Zone* zone() const { return zone_; } |
| 300 | |
| 301 | IsolateGroupReloadContext* group_reload_context() { |
| 302 | return group_reload_context_.get(); |
| 303 | } |
| 304 | |
| 305 | static bool IsSameLibrary(const Library& a_lib, const Library& b_lib); |
| 306 | static bool IsSameClass(const Class& a, const Class& b); |
| 307 | |
| 308 | private: |
| 309 | bool IsDirty(const Library& lib); |
| 310 | |
| 311 | void RegisterClass(const Class& new_cls); |
| 312 | |
| 313 | // Finds the library private key for |replacement_or_new| or return null |
| 314 | // if |replacement_or_new| is new. |
| 315 | StringPtr FindLibraryPrivateKey(const Library& replacement_or_new); |
| 316 | |
| 317 | void VisitObjectPointers(ObjectPointerVisitor* visitor); |
| 318 | |
| 319 | IsolateGroup* isolate_group() { return isolate_group_; } |
| 320 | ObjectStore* object_store(); |
| 321 | |
| 322 | void EnsuredUnoptimizedCodeForStack(); |
| 323 | void DeoptimizeDependentCode(); |
| 324 | |
| 325 | void ReloadPhase1AllocateStorageMapsAndCheckpoint(); |
| 326 | void CheckpointClasses(); |
| 327 | ObjectPtr ReloadPhase2LoadKernel(kernel::Program* program, |
| 328 | const String& root_lib_url); |
| 329 | void ReloadPhase3FinalizeLoading(); |
| 330 | void ReloadPhase4CommitPrepare(); |
| 331 | void ReloadPhase4CommitFinish(); |
| 332 | void ReloadPhase4Rollback(); |
| 333 | |
| 334 | void CheckpointLibraries(); |
| 335 | |
| 336 | void RollbackLibraries(); |
| 337 | |
| 338 | #ifdef DEBUG |
| 339 | void VerifyMaps(); |
| 340 | #endif |
| 341 | |
| 342 | void CommitBeforeInstanceMorphing(); |
| 343 | void CommitAfterInstanceMorphing(); |
| 344 | void PostCommit(); |
| 345 | |
| 346 | void RunInvalidationVisitors(); |
| 347 | void InvalidateKernelInfos( |
| 348 | Zone* zone, |
| 349 | const GrowableArray<const KernelProgramInfo*>& kernel_infos); |
| 350 | void InvalidateFunctions(Zone* zone, |
| 351 | const GrowableArray<const Function*>& functions); |
| 352 | void InvalidateSuspendStates( |
| 353 | Zone* zone, |
| 354 | const GrowableArray<const SuspendState*>& suspend_states); |
| 355 | void InvalidateFields(Zone* zone, |
| 356 | const GrowableArray<const Field*>& fields, |
| 357 | const GrowableArray<const Instance*>& instances); |
| 358 | void ResetUnoptimizedICsOnStack(); |
| 359 | void ResetMegamorphicCaches(); |
| 360 | void InvalidateWorld(); |
| 361 | |
| 362 | struct LibraryInfo { |
| 363 | bool dirty; |
| 364 | }; |
| 365 | |
| 366 | // The zone used for all reload related allocations. |
| 367 | Zone* zone_; |
| 368 | std::shared_ptr<IsolateGroupReloadContext> group_reload_context_; |
| 369 | IsolateGroup* isolate_group_; |
| 370 | MallocGrowableArray<LibraryInfo> library_infos_; |
| 371 | |
| 372 | ClassPtr OldClassOrNull(const Class& replacement_or_new); |
| 373 | LibraryPtr OldLibraryOrNull(const Library& replacement_or_new); |
| 374 | LibraryPtr OldLibraryOrNullBaseMoved(const Library& replacement_or_new); |
| 375 | |
| 376 | void BuildLibraryMapping(); |
| 377 | void BuildRemovedClassesSet(); |
| 378 | void ValidateReload(); |
| 379 | |
| 380 | void AddClassMapping(const Class& replacement_or_new, const Class& original); |
| 381 | void AddLibraryMapping(const Library& replacement_or_new, |
| 382 | const Library& original); |
| 383 | void AddStaticFieldMapping(const Field& old_field, const Field& new_field); |
| 384 | void AddBecomeMapping(const Object& old, const Object& neu); |
| 385 | void RebuildDirectSubclasses(); |
| 386 | |
| 387 | Become become_; |
| 388 | |
| 389 | ObjectPtr* from() { |
| 390 | return reinterpret_cast<ObjectPtr*>(&old_classes_set_storage_); |
| 391 | } |
| 392 | ArrayPtr old_classes_set_storage_; |
| 393 | ArrayPtr class_map_storage_; |
| 394 | ArrayPtr removed_class_set_storage_; |
| 395 | ArrayPtr old_libraries_set_storage_; |
| 396 | ArrayPtr library_map_storage_; |
| 397 | LibraryPtr saved_root_library_; |
| 398 | GrowableObjectArrayPtr saved_libraries_; |
| 399 | ObjectPtr* to() { return reinterpret_cast<ObjectPtr*>(&saved_libraries_); } |
| 400 | |
| 401 | friend class Isolate; |
| 402 | friend class IsolateGroup; |
| 403 | friend class Class; // AddStaticFieldMapping, AddEnumBecomeMapping. |
| 404 | friend class Library; |
| 405 | friend class ObjectLocator; |
| 406 | friend class ReasonForCancelling; |
| 407 | friend class IsolateGroupReloadContext; |
| 408 | }; |
| 409 | |
| 410 | class CallSiteResetter : public ValueObject { |
| 411 | public: |
| 412 | explicit CallSiteResetter(Zone* zone); |
| 413 | |
| 414 | void ZeroEdgeCounters(const Function& function); |
| 415 | void ResetCaches(const Code& code); |
| 416 | void ResetCaches(const ObjectPool& pool); |
| 417 | void Reset(const ICData& ic); |
| 418 | void ResetSwitchableCalls(const Code& code); |
| 419 | |
| 420 | private: |
| 421 | Zone* zone_; |
| 422 | Instructions& instrs_; |
| 423 | ObjectPool& pool_; |
| 424 | Object& object_; |
| 425 | String& name_; |
| 426 | Class& new_cls_; |
| 427 | Library& new_lib_; |
| 428 | Function& new_function_; |
| 429 | Field& new_field_; |
| 430 | Array& entries_; |
| 431 | Function& old_target_; |
| 432 | Function& new_target_; |
| 433 | Function& caller_; |
| 434 | Array& args_desc_array_; |
| 435 | Array& ic_data_array_; |
| 436 | Array& edge_counters_; |
| 437 | PcDescriptors& descriptors_; |
| 438 | ICData& ic_data_; |
| 439 | }; |
| 440 | |
| 441 | // Ensures all other mutators are stopped at a well-defined place where reload |
| 442 | // is allowed. |
| 443 | #define RELOAD_OPERATION_SCOPE(thread_expr) \ |
| 444 | auto _thread_ = (thread_expr); \ |
| 445 | \ |
| 446 | /* As the background compiler is a mutator it participates in safepoint */ \ |
| 447 | /* operations. Though the BG compiler won't check into reload safepoint */ \ |
| 448 | /* requests - as it's not a well-defined place to do reload. */ \ |
| 449 | /* So we ensure the background compiler is stopped before we get all */ \ |
| 450 | /* other mutators to reload safepoints. */ \ |
| 451 | NoBackgroundCompilerScope _stop_bg_compiler_(_thread_); \ |
| 452 | \ |
| 453 | /* This will enable the current thread to perform reload operations (as */ \ |
| 454 | /* well as check-in with other thread's reload operations). */ \ |
| 455 | ReloadParticipationScope _allow_reload_(_thread_); \ |
| 456 | \ |
| 457 | /* The actual reload operation that will ensure all other mutators are */ \ |
| 458 | /* stopped at well-defined places where reload can happen. */ \ |
| 459 | ReloadSafepointOperationScope _safepoint_operation_(_thread_); |
| 460 | |
| 461 | } // namespace dart |
| 462 | |
| 463 | #endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME) |
| 464 | |
| 465 | #endif // RUNTIME_VM_ISOLATE_RELOAD_H_ |
| 466 | |