1 | // Copyright 2013 The Flutter Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | #include "flutter/runtime/dart_vm.h" |
6 | |
7 | #include <sys/stat.h> |
8 | |
9 | #include <mutex> |
10 | #include <sstream> |
11 | #include <vector> |
12 | |
13 | #include "flutter/common/settings.h" |
14 | #include "flutter/fml/compiler_specific.h" |
15 | #include "flutter/fml/file.h" |
16 | #include "flutter/fml/logging.h" |
17 | #include "flutter/fml/mapping.h" |
18 | #include "flutter/fml/size.h" |
19 | #include "flutter/fml/synchronization/count_down_latch.h" |
20 | #include "flutter/fml/time/time_delta.h" |
21 | #include "flutter/fml/trace_event.h" |
22 | #include "flutter/lib/io/dart_io.h" |
23 | #include "flutter/lib/ui/dart_runtime_hooks.h" |
24 | #include "flutter/lib/ui/dart_ui.h" |
25 | #include "flutter/runtime/dart_isolate.h" |
26 | #include "flutter/runtime/dart_service_isolate.h" |
27 | #include "flutter/runtime/ptrace_ios.h" |
28 | #include "third_party/dart/runtime/include/bin/dart_io_api.h" |
29 | #include "third_party/skia/include/core/SkExecutor.h" |
30 | #include "third_party/tonic/converter/dart_converter.h" |
31 | #include "third_party/tonic/dart_class_library.h" |
32 | #include "third_party/tonic/dart_class_provider.h" |
33 | #include "third_party/tonic/file_loader/file_loader.h" |
34 | #include "third_party/tonic/logging/dart_error.h" |
35 | #include "third_party/tonic/scopes/dart_api_scope.h" |
36 | #include "third_party/tonic/typed_data/typed_list.h" |
37 | |
38 | namespace dart { |
39 | namespace observatory { |
40 | |
41 | #if !OS_FUCHSIA && !FLUTTER_RELEASE |
42 | |
43 | // These two symbols are defined in |observatory_archive.cc| which is generated |
44 | // by the |//third_party/dart/runtime/observatory:archive_observatory| rule. |
45 | // Both of these symbols will be part of the data segment and therefore are read |
46 | // only. |
47 | extern unsigned int observatory_assets_archive_len; |
48 | extern const uint8_t* observatory_assets_archive; |
49 | |
50 | #endif // !OS_FUCHSIA && !FLUTTER_RELEASE |
51 | |
52 | } // namespace observatory |
53 | } // namespace dart |
54 | |
55 | namespace flutter { |
56 | |
57 | // Arguments passed to the Dart VM in all configurations. |
58 | static const char* kDartLanguageArgs[] = { |
59 | // clang-format off |
60 | "--enable_mirrors=false" , |
61 | "--background_compilation" , |
62 | "--causal_async_stacks" , |
63 | // clang-format on |
64 | }; |
65 | |
66 | static const char* kDartPrecompilationArgs[] = { |
67 | "--precompilation" , |
68 | }; |
69 | |
70 | FML_ALLOW_UNUSED_TYPE |
71 | static const char* kDartWriteProtectCodeArgs[] = { |
72 | "--no_write_protect_code" , |
73 | }; |
74 | |
75 | FML_ALLOW_UNUSED_TYPE |
76 | static const char* kDartDisableIntegerDivisionArgs[] = { |
77 | "--no_use_integer_division" , |
78 | }; |
79 | |
80 | static const char* kDartAssertArgs[] = { |
81 | // clang-format off |
82 | "--enable_asserts" , |
83 | // clang-format on |
84 | }; |
85 | |
86 | static const char* kDartStartPausedArgs[]{ |
87 | "--pause_isolates_on_start" , |
88 | }; |
89 | |
90 | static const char* kDartDisableServiceAuthCodesArgs[]{ |
91 | "--disable-service-auth-codes" , |
92 | }; |
93 | |
94 | static const char* kDartTraceStartupArgs[]{ |
95 | "--timeline_streams=Compiler,Dart,Debugger,Embedder,GC,Isolate,VM,API" , |
96 | }; |
97 | |
98 | static const char* kDartEndlessTraceBufferArgs[]{ |
99 | "--timeline_recorder=endless" , |
100 | }; |
101 | |
102 | static const char* kDartSystraceTraceBufferArgs[]{ |
103 | "--timeline_recorder=systrace" , |
104 | }; |
105 | |
106 | static const char* kDartFuchsiaTraceArgs[] FML_ALLOW_UNUSED_TYPE = { |
107 | "--systrace_timeline" , |
108 | }; |
109 | |
110 | static const char* kDartTraceStreamsArgs[] = { |
111 | "--timeline_streams=Compiler,Dart,Debugger,Embedder,GC,Isolate,VM,API" , |
112 | }; |
113 | |
114 | static std::string DartOldGenHeapSizeArgs(uint64_t heap_size) { |
115 | std::ostringstream oss; |
116 | oss << "--old_gen_heap_size=" << heap_size; |
117 | return oss.str(); |
118 | } |
119 | |
120 | constexpr char kFileUriPrefix[] = "file://" ; |
121 | constexpr size_t kFileUriPrefixLength = sizeof(kFileUriPrefix) - 1; |
122 | |
123 | bool DartFileModifiedCallback(const char* source_url, int64_t since_ms) { |
124 | if (strncmp(source_url, kFileUriPrefix, kFileUriPrefixLength) != 0u) { |
125 | // Assume modified. |
126 | return true; |
127 | } |
128 | |
129 | const char* path = source_url + kFileUriPrefixLength; |
130 | struct stat info; |
131 | if (stat(path, &info) < 0) { |
132 | return true; |
133 | } |
134 | |
135 | // If st_mtime is zero, it's more likely that the file system doesn't support |
136 | // mtime than that the file was actually modified in the 1970s. |
137 | if (!info.st_mtime) { |
138 | return true; |
139 | } |
140 | |
141 | // It's very unclear what time bases we're with here. The Dart API doesn't |
142 | // document the time base for since_ms. Reading the code, the value varies by |
143 | // platform, with a typical source being something like gettimeofday. |
144 | // |
145 | // We add one to st_mtime because st_mtime has less precision than since_ms |
146 | // and we want to treat the file as modified if the since time is between |
147 | // ticks of the mtime. |
148 | fml::TimeDelta mtime = fml::TimeDelta::FromSeconds(info.st_mtime + 1); |
149 | fml::TimeDelta since = fml::TimeDelta::FromMilliseconds(since_ms); |
150 | |
151 | return mtime > since; |
152 | } |
153 | |
154 | void ThreadExitCallback() {} |
155 | |
156 | Dart_Handle GetVMServiceAssetsArchiveCallback() { |
157 | #if FLUTTER_RELEASE |
158 | return nullptr; |
159 | #elif OS_FUCHSIA |
160 | fml::UniqueFD fd = fml::OpenFile("pkg/data/observatory.tar" , false, |
161 | fml::FilePermission::kRead); |
162 | fml::FileMapping mapping(fd, {fml::FileMapping::Protection::kRead}); |
163 | if (mapping.GetSize() == 0 || mapping.GetMapping() == nullptr) { |
164 | FML_LOG(ERROR) << "Fail to load Observatory archive" ; |
165 | return nullptr; |
166 | } |
167 | return tonic::DartConverter<tonic::Uint8List>::ToDart(mapping.GetMapping(), |
168 | mapping.GetSize()); |
169 | #else |
170 | return tonic::DartConverter<tonic::Uint8List>::ToDart( |
171 | ::dart::observatory::observatory_assets_archive, |
172 | ::dart::observatory::observatory_assets_archive_len); |
173 | #endif |
174 | } |
175 | |
176 | static const char kStdoutStreamId[] = "Stdout" ; |
177 | static const char kStderrStreamId[] = "Stderr" ; |
178 | |
179 | static bool ServiceStreamListenCallback(const char* stream_id) { |
180 | if (strcmp(stream_id, kStdoutStreamId) == 0) { |
181 | dart::bin::SetCaptureStdout(true); |
182 | return true; |
183 | } else if (strcmp(stream_id, kStderrStreamId) == 0) { |
184 | dart::bin::SetCaptureStderr(true); |
185 | return true; |
186 | } |
187 | return false; |
188 | } |
189 | |
190 | static void ServiceStreamCancelCallback(const char* stream_id) { |
191 | if (strcmp(stream_id, kStdoutStreamId) == 0) { |
192 | dart::bin::SetCaptureStdout(false); |
193 | } else if (strcmp(stream_id, kStderrStreamId) == 0) { |
194 | dart::bin::SetCaptureStderr(false); |
195 | } |
196 | } |
197 | |
198 | bool DartVM::IsRunningPrecompiledCode() { |
199 | return Dart_IsPrecompiledRuntime(); |
200 | } |
201 | |
202 | static std::vector<const char*> ProfilingFlags(bool enable_profiling) { |
203 | // Disable Dart's built in profiler when building a debug build. This |
204 | // works around a race condition that would sometimes stop a crash's |
205 | // stack trace from being printed on Android. |
206 | #ifndef NDEBUG |
207 | enable_profiling = false; |
208 | #endif |
209 | |
210 | // We want to disable profiling by default because it overwhelms LLDB. But |
211 | // the VM enables the same by default. In either case, we have some profiling |
212 | // flags. |
213 | if (enable_profiling) { |
214 | return {// This is the default. But just be explicit. |
215 | "--profiler" , |
216 | // This instructs the profiler to walk C++ frames, and to include |
217 | // them in the profile. |
218 | "--profile-vm" }; |
219 | } else { |
220 | return {"--no-profiler" }; |
221 | } |
222 | } |
223 | |
224 | void PushBackAll(std::vector<const char*>* args, |
225 | const char** argv, |
226 | size_t argc) { |
227 | for (size_t i = 0; i < argc; ++i) { |
228 | args->push_back(argv[i]); |
229 | } |
230 | } |
231 | |
232 | static void EmbedderInformationCallback(Dart_EmbedderInformation* info) { |
233 | info->version = DART_EMBEDDER_INFORMATION_CURRENT_VERSION; |
234 | dart::bin::GetIOEmbedderInformation(info); |
235 | info->name = "Flutter" ; |
236 | } |
237 | |
238 | std::shared_ptr<DartVM> DartVM::Create( |
239 | Settings settings, |
240 | fml::RefPtr<DartSnapshot> vm_snapshot, |
241 | fml::RefPtr<DartSnapshot> isolate_snapshot, |
242 | std::shared_ptr<IsolateNameServer> isolate_name_server) { |
243 | auto vm_data = DartVMData::Create(settings, // |
244 | std::move(vm_snapshot), // |
245 | std::move(isolate_snapshot) // |
246 | ); |
247 | |
248 | if (!vm_data) { |
249 | FML_LOG(ERROR) << "Could not setup VM data to bootstrap the VM from." ; |
250 | return {}; |
251 | } |
252 | |
253 | // Note: std::make_shared unviable due to hidden constructor. |
254 | return std::shared_ptr<DartVM>( |
255 | new DartVM(std::move(vm_data), std::move(isolate_name_server))); |
256 | } |
257 | |
258 | static std::atomic_size_t gVMLaunchCount; |
259 | |
260 | size_t DartVM::GetVMLaunchCount() { |
261 | return gVMLaunchCount; |
262 | } |
263 | |
264 | DartVM::DartVM(std::shared_ptr<const DartVMData> vm_data, |
265 | std::shared_ptr<IsolateNameServer> isolate_name_server) |
266 | : settings_(vm_data->GetSettings()), |
267 | concurrent_message_loop_(fml::ConcurrentMessageLoop::Create()), |
268 | skia_concurrent_executor_( |
269 | [runner = concurrent_message_loop_->GetTaskRunner()]( |
270 | fml::closure work) { runner->PostTask(work); }), |
271 | vm_data_(vm_data), |
272 | isolate_name_server_(std::move(isolate_name_server)), |
273 | service_protocol_(std::make_shared<ServiceProtocol>()) { |
274 | TRACE_EVENT0("flutter" , "DartVMInitializer" ); |
275 | |
276 | gVMLaunchCount++; |
277 | |
278 | // Setting the executor is not thread safe but Dart VM initialization is. So |
279 | // this call is thread-safe. |
280 | SkExecutor::SetDefault(&skia_concurrent_executor_); |
281 | |
282 | FML_DCHECK(vm_data_); |
283 | FML_DCHECK(isolate_name_server_); |
284 | FML_DCHECK(service_protocol_); |
285 | |
286 | { |
287 | TRACE_EVENT0("flutter" , "dart::bin::BootstrapDartIo" ); |
288 | dart::bin::BootstrapDartIo(); |
289 | |
290 | if (!settings_.temp_directory_path.empty()) { |
291 | dart::bin::SetSystemTempDirectory(settings_.temp_directory_path.c_str()); |
292 | } |
293 | } |
294 | |
295 | std::vector<const char*> args; |
296 | |
297 | // Instruct the VM to ignore unrecognized flags. |
298 | // There is a lot of diversity in a lot of combinations when it |
299 | // comes to the arguments the VM supports. And, if the VM comes across a flag |
300 | // it does not recognize, it exits immediately. |
301 | args.push_back("--ignore-unrecognized-flags" ); |
302 | |
303 | for (auto* const profiler_flag : |
304 | ProfilingFlags(settings_.enable_dart_profiling)) { |
305 | args.push_back(profiler_flag); |
306 | } |
307 | |
308 | PushBackAll(&args, kDartLanguageArgs, fml::size(kDartLanguageArgs)); |
309 | |
310 | if (IsRunningPrecompiledCode()) { |
311 | PushBackAll(&args, kDartPrecompilationArgs, |
312 | fml::size(kDartPrecompilationArgs)); |
313 | } |
314 | |
315 | // Enable Dart assertions if we are not running precompiled code. We run non- |
316 | // precompiled code only in the debug product mode. |
317 | bool enable_asserts = !settings_.disable_dart_asserts; |
318 | |
319 | #if !OS_FUCHSIA |
320 | if (IsRunningPrecompiledCode()) { |
321 | enable_asserts = false; |
322 | } |
323 | #endif // !OS_FUCHSIA |
324 | |
325 | #if (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG) |
326 | #if !OS_IOS || TARGET_OS_SIMULATOR |
327 | // Debug mode uses the JIT, disable code page write protection to avoid |
328 | // memory page protection changes before and after every compilation. |
329 | PushBackAll(&args, kDartWriteProtectCodeArgs, |
330 | fml::size(kDartWriteProtectCodeArgs)); |
331 | #else |
332 | EnsureDebuggedIOS(settings_); |
333 | #if TARGET_CPU_ARM |
334 | // Tell Dart in JIT mode to not use integer division on armv7 |
335 | // Ideally, this would be detected at runtime by Dart. |
336 | // TODO(dnfield): Remove this code |
337 | // https://github.com/dart-lang/sdk/issues/24743 |
338 | PushBackAll(&args, kDartDisableIntegerDivisionArgs, |
339 | fml::size(kDartDisableIntegerDivisionArgs)); |
340 | #endif // TARGET_CPU_ARM |
341 | #endif // !OS_IOS || TARGET_OS_SIMULATOR |
342 | #endif // (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG) |
343 | |
344 | if (enable_asserts) { |
345 | PushBackAll(&args, kDartAssertArgs, fml::size(kDartAssertArgs)); |
346 | } |
347 | |
348 | if (settings_.start_paused) { |
349 | PushBackAll(&args, kDartStartPausedArgs, fml::size(kDartStartPausedArgs)); |
350 | } |
351 | |
352 | if (settings_.disable_service_auth_codes) { |
353 | PushBackAll(&args, kDartDisableServiceAuthCodesArgs, |
354 | fml::size(kDartDisableServiceAuthCodesArgs)); |
355 | } |
356 | |
357 | if (settings_.endless_trace_buffer || settings_.trace_startup) { |
358 | // If we are tracing startup, make sure the trace buffer is endless so we |
359 | // don't lose early traces. |
360 | PushBackAll(&args, kDartEndlessTraceBufferArgs, |
361 | fml::size(kDartEndlessTraceBufferArgs)); |
362 | } |
363 | |
364 | if (settings_.trace_systrace) { |
365 | PushBackAll(&args, kDartSystraceTraceBufferArgs, |
366 | fml::size(kDartSystraceTraceBufferArgs)); |
367 | PushBackAll(&args, kDartTraceStreamsArgs, fml::size(kDartTraceStreamsArgs)); |
368 | } |
369 | |
370 | if (settings_.trace_startup) { |
371 | PushBackAll(&args, kDartTraceStartupArgs, fml::size(kDartTraceStartupArgs)); |
372 | } |
373 | |
374 | std::string old_gen_heap_size_args; |
375 | if (settings_.old_gen_heap_size >= 0) { |
376 | old_gen_heap_size_args = |
377 | DartOldGenHeapSizeArgs(settings_.old_gen_heap_size); |
378 | args.push_back(old_gen_heap_size_args.c_str()); |
379 | } |
380 | |
381 | #if defined(OS_FUCHSIA) && \ |
382 | (FLUTTER_RUNTIME_MODE != FLUTTER_RUNTIME_MODE_PROFILE) |
383 | PushBackAll(&args, kDartFuchsiaTraceArgs, fml::size(kDartFuchsiaTraceArgs)); |
384 | PushBackAll(&args, kDartTraceStreamsArgs, fml::size(kDartTraceStreamsArgs)); |
385 | #endif |
386 | |
387 | for (size_t i = 0; i < settings_.dart_flags.size(); i++) { |
388 | args.push_back(settings_.dart_flags[i].c_str()); |
389 | } |
390 | |
391 | char* flags_error = Dart_SetVMFlags(args.size(), args.data()); |
392 | if (flags_error) { |
393 | FML_LOG(FATAL) << "Error while setting Dart VM flags: " << flags_error; |
394 | ::free(flags_error); |
395 | } |
396 | |
397 | DartUI::InitForGlobal(); |
398 | |
399 | { |
400 | TRACE_EVENT0("flutter" , "Dart_Initialize" ); |
401 | Dart_InitializeParams params = {}; |
402 | params.version = DART_INITIALIZE_PARAMS_CURRENT_VERSION; |
403 | params.vm_snapshot_data = vm_data_->GetVMSnapshot().GetDataMapping(); |
404 | params.vm_snapshot_instructions = |
405 | vm_data_->GetVMSnapshot().GetInstructionsMapping(); |
406 | params.create_group = reinterpret_cast<decltype(params.create_group)>( |
407 | DartIsolate::DartIsolateGroupCreateCallback); |
408 | params.initialize_isolate = |
409 | reinterpret_cast<decltype(params.initialize_isolate)>( |
410 | DartIsolate::DartIsolateInitializeCallback); |
411 | params.shutdown_isolate = |
412 | reinterpret_cast<decltype(params.shutdown_isolate)>( |
413 | DartIsolate::DartIsolateShutdownCallback); |
414 | params.cleanup_isolate = reinterpret_cast<decltype(params.cleanup_isolate)>( |
415 | DartIsolate::DartIsolateCleanupCallback); |
416 | params.cleanup_group = reinterpret_cast<decltype(params.cleanup_group)>( |
417 | DartIsolate::DartIsolateGroupCleanupCallback); |
418 | params.thread_exit = ThreadExitCallback; |
419 | params.get_service_assets = GetVMServiceAssetsArchiveCallback; |
420 | params.entropy_source = dart::bin::GetEntropy; |
421 | char* init_error = Dart_Initialize(¶ms); |
422 | if (init_error) { |
423 | FML_LOG(FATAL) << "Error while initializing the Dart VM: " << init_error; |
424 | ::free(init_error); |
425 | } |
426 | // Send the earliest available timestamp in the application lifecycle to |
427 | // timeline. The difference between this timestamp and the time we render |
428 | // the very first frame gives us a good idea about Flutter's startup time. |
429 | // Use a duration event so about:tracing will consider this event when |
430 | // deciding the earliest event to use as time 0. |
431 | if (settings_.engine_start_timestamp.count()) { |
432 | Dart_TimelineEvent( |
433 | "FlutterEngineMainEnter" , // label |
434 | settings_.engine_start_timestamp.count(), // timestamp0 |
435 | Dart_TimelineGetMicros(), // timestamp1_or_async_id |
436 | Dart_Timeline_Event_Duration, // event type |
437 | 0, // argument_count |
438 | nullptr, // argument_names |
439 | nullptr // argument_values |
440 | ); |
441 | } |
442 | } |
443 | |
444 | Dart_SetFileModifiedCallback(&DartFileModifiedCallback); |
445 | |
446 | // Allow streaming of stdout and stderr by the Dart vm. |
447 | Dart_SetServiceStreamCallbacks(&ServiceStreamListenCallback, |
448 | &ServiceStreamCancelCallback); |
449 | |
450 | Dart_SetEmbedderInformationCallback(&EmbedderInformationCallback); |
451 | |
452 | if (settings_.dart_library_sources_kernel != nullptr) { |
453 | std::unique_ptr<fml::Mapping> dart_library_sources = |
454 | settings_.dart_library_sources_kernel(); |
455 | // Set sources for dart:* libraries for debugging. |
456 | Dart_SetDartLibrarySourcesKernel(dart_library_sources->GetMapping(), |
457 | dart_library_sources->GetSize()); |
458 | } |
459 | } |
460 | |
461 | DartVM::~DartVM() { |
462 | // Setting the executor is not thread safe but Dart VM shutdown is. So |
463 | // this call is thread-safe. |
464 | SkExecutor::SetDefault(nullptr); |
465 | |
466 | if (Dart_CurrentIsolate() != nullptr) { |
467 | Dart_ExitIsolate(); |
468 | } |
469 | |
470 | char* result = Dart_Cleanup(); |
471 | |
472 | dart::bin::CleanupDartIo(); |
473 | |
474 | FML_CHECK(result == nullptr) |
475 | << "Could not cleanly shut down the Dart VM. Error: \"" << result |
476 | << "\"." ; |
477 | free(result); |
478 | } |
479 | |
480 | std::shared_ptr<const DartVMData> DartVM::GetVMData() const { |
481 | return vm_data_; |
482 | } |
483 | |
484 | const Settings& DartVM::GetSettings() const { |
485 | return settings_; |
486 | } |
487 | |
488 | std::shared_ptr<ServiceProtocol> DartVM::GetServiceProtocol() const { |
489 | return service_protocol_; |
490 | } |
491 | |
492 | std::shared_ptr<IsolateNameServer> DartVM::GetIsolateNameServer() const { |
493 | return isolate_name_server_; |
494 | } |
495 | |
496 | std::shared_ptr<fml::ConcurrentTaskRunner> |
497 | DartVM::GetConcurrentWorkerTaskRunner() const { |
498 | return concurrent_message_loop_->GetTaskRunner(); |
499 | } |
500 | |
501 | std::shared_ptr<fml::ConcurrentMessageLoop> DartVM::GetConcurrentMessageLoop() { |
502 | return concurrent_message_loop_; |
503 | } |
504 | |
505 | } // namespace flutter |
506 | |