// Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "flutter/runtime/dart_vm.h" #include #include #include #include "flutter/common/settings.h" #include "flutter/fml/cpu_affinity.h" #include "flutter/fml/logging.h" #include "flutter/fml/mapping.h" #include "flutter/fml/time/time_delta.h" #include "flutter/fml/trace_event.h" #include "flutter/lib/ui/dart_ui.h" #include "flutter/runtime/dart_isolate.h" #include "flutter/runtime/dart_vm_initializer.h" #include "flutter/runtime/ptrace_check.h" #include "third_party/dart/runtime/include/bin/dart_io_api.h" #include "third_party/skia/include/core/SkExecutor.h" #include "third_party/tonic/converter/dart_converter.h" #include "third_party/tonic/dart_class_library.h" #include "third_party/tonic/dart_class_provider.h" #include "third_party/tonic/file_loader/file_loader.h" #include "third_party/tonic/logging/dart_error.h" #include "third_party/tonic/typed_data/typed_list.h" namespace dart { namespace observatory { #if !OS_FUCHSIA && !FLUTTER_RELEASE // These two symbols are defined in |observatory_archive.cc| which is generated // by the |//third_party/dart/runtime/observatory:archive_observatory| rule. // Both of these symbols will be part of the data segment and therefore are read // only. extern unsigned int observatory_assets_archive_len; extern const uint8_t* observatory_assets_archive; #endif // !OS_FUCHSIA && !FLUTTER_RELEASE } // namespace observatory } // namespace dart namespace flutter { // Arguments passed to the Dart VM in all configurations. static const char* kDartAllConfigsArgs[] = { // clang-format off "--enable_mirrors=false", "--background_compilation", // 'mark_when_idle' appears to cause a regression, turning off for now. // "--mark_when_idle", // clang-format on }; static const char* kDartPrecompilationArgs[] = {"--precompilation"}; static const char* kSerialGCArgs[] = { // clang-format off "--concurrent_mark=false", "--concurrent_sweep=false", "--compactor_tasks=1", "--scavenger_tasks=0", "--marker_tasks=0", // clang-format on }; [[maybe_unused]] static const char* kDartWriteProtectCodeArgs[] = { "--no_write_protect_code", }; [[maybe_unused]] static const char* kDartDisableIntegerDivisionArgs[] = { "--no_use_integer_division", }; static const char* kDartAssertArgs[] = { // clang-format off "--enable_asserts", // clang-format on }; static const char* kDartStartPausedArgs[]{ "--pause_isolates_on_start", }; static const char* kDartEndlessTraceBufferArgs[]{ "--timeline_recorder=endless", }; static const char* kDartSystraceTraceBufferArgs[] = { "--timeline_recorder=systrace", }; static std::string DartFileRecorderArgs(const std::string& path) { std::ostringstream oss; oss << "--timeline_recorder=perfettofile:" << path; return oss.str(); } [[maybe_unused]] static const char* kDartDefaultTraceStreamsArgs[]{ "--timeline_streams=Dart,Embedder,GC", }; static const char* kDartStartupTraceStreamsArgs[]{ "--timeline_streams=Compiler,Dart,Debugger,Embedder,GC,Isolate,VM,API", }; static const char* kDartSystraceTraceStreamsArgs[] = { "--timeline_streams=Compiler,Dart,Debugger,Embedder,GC,Isolate,VM,API", }; static std::string DartOldGenHeapSizeArgs(uint64_t heap_size) { std::ostringstream oss; oss << "--old_gen_heap_size=" << heap_size; return oss.str(); } constexpr char kFileUriPrefix[] = "file://"; constexpr size_t kFileUriPrefixLength = sizeof(kFileUriPrefix) - 1; bool DartFileModifiedCallback(const char* source_url, int64_t since_ms) { if (strncmp(source_url, kFileUriPrefix, kFileUriPrefixLength) != 0u) { // Assume modified. return true; } const char* path = source_url + kFileUriPrefixLength; struct stat info; if (stat(path, &info) < 0) { return true; } // If st_mtime is zero, it's more likely that the file system doesn't support // mtime than that the file was actually modified in the 1970s. if (!info.st_mtime) { return true; } // It's very unclear what time bases we're with here. The Dart API doesn't // document the time base for since_ms. Reading the code, the value varies by // platform, with a typical source being something like gettimeofday. // // We add one to st_mtime because st_mtime has less precision than since_ms // and we want to treat the file as modified if the since time is between // ticks of the mtime. fml::TimeDelta mtime = fml::TimeDelta::FromSeconds(info.st_mtime + 1); fml::TimeDelta since = fml::TimeDelta::FromMilliseconds(since_ms); return mtime > since; } void ThreadExitCallback() {} Dart_Handle GetVMServiceAssetsArchiveCallback() { #if FLUTTER_RELEASE return nullptr; #elif OS_FUCHSIA fml::UniqueFD fd = fml::OpenFile("pkg/data/observatory.tar", false, fml::FilePermission::kRead); fml::FileMapping mapping(fd, {fml::FileMapping::Protection::kRead}); if (mapping.GetSize() == 0 || mapping.GetMapping() == nullptr) { FML_LOG(ERROR) << "Fail to load Observatory archive"; return nullptr; } return tonic::DartConverter::ToDart(mapping.GetMapping(), mapping.GetSize()); #else return tonic::DartConverter::ToDart( ::dart::observatory::observatory_assets_archive, ::dart::observatory::observatory_assets_archive_len); #endif } static const char kStdoutStreamId[] = "Stdout"; static const char kStderrStreamId[] = "Stderr"; static bool ServiceStreamListenCallback(const char* stream_id) { if (strcmp(stream_id, kStdoutStreamId) == 0) { dart::bin::SetCaptureStdout(true); return true; } else if (strcmp(stream_id, kStderrStreamId) == 0) { dart::bin::SetCaptureStderr(true); return true; } return false; } static void ServiceStreamCancelCallback(const char* stream_id) { if (strcmp(stream_id, kStdoutStreamId) == 0) { dart::bin::SetCaptureStdout(false); } else if (strcmp(stream_id, kStderrStreamId) == 0) { dart::bin::SetCaptureStderr(false); } } bool DartVM::IsRunningPrecompiledCode() { return Dart_IsPrecompiledRuntime(); } static std::vector ProfilingFlags(bool enable_profiling) { // Disable Dart's built in profiler when building a debug build. This // works around a race condition that would sometimes stop a crash's // stack trace from being printed on Android. #ifndef NDEBUG enable_profiling = false; #endif // We want to disable profiling by default because it overwhelms LLDB. But // the VM enables the same by default. In either case, we have some profiling // flags. if (enable_profiling) { return { // This is the default. But just be explicit. "--profiler", // This instructs the profiler to walk C++ frames, and to include // them in the profile. "--profile-vm", #if FML_OS_IOS && FML_ARCH_CPU_ARM_FAMILY && FML_ARCH_CPU_ARMEL // Set the profiler interrupt period to 500Hz instead of the // default 1000Hz on 32-bit iOS devices to reduce average and worst // case frame build times. // // Note: profile_period is time in microseconds between sampling // events, not frequency. Frequency is calculated 1/period (or // 1,000,000 / 2,000 -> 500Hz in this case). "--profile_period=2000", #else "--profile_period=1000", #endif // FML_OS_IOS && FML_ARCH_CPU_ARM_FAMILY && FML_ARCH_CPU_ARMEL }; } else { return {"--no-profiler"}; } } void PushBackAll(std::vector* args, const char** argv, size_t argc) { for (size_t i = 0; i < argc; ++i) { args->push_back(argv[i]); } } static void EmbedderInformationCallback(Dart_EmbedderInformation* info) { info->version = DART_EMBEDDER_INFORMATION_CURRENT_VERSION; dart::bin::GetIOEmbedderInformation(info); info->name = "Flutter"; } std::shared_ptr DartVM::Create( const Settings& settings, fml::RefPtr vm_snapshot, fml::RefPtr isolate_snapshot, std::shared_ptr isolate_name_server) { auto vm_data = DartVMData::Create(settings, // std::move(vm_snapshot), // std::move(isolate_snapshot) // ); if (!vm_data) { FML_LOG(ERROR) << "Could not set up VM data to bootstrap the VM from."; return {}; } // Note: std::make_shared unviable due to hidden constructor. return std::shared_ptr( new DartVM(vm_data, std::move(isolate_name_server))); } static std::atomic_size_t gVMLaunchCount; size_t DartVM::GetVMLaunchCount() { return gVMLaunchCount; } // Minimum and maximum number of worker threads. static constexpr size_t kMinCount = 2; static constexpr size_t kMaxCount = 4; DartVM::DartVM(const std::shared_ptr& vm_data, std::shared_ptr isolate_name_server) : settings_(vm_data->GetSettings()), concurrent_message_loop_(fml::ConcurrentMessageLoop::Create( std::clamp(fml::EfficiencyCoreCount().value_or( std::thread::hardware_concurrency()) / 2, kMinCount, kMaxCount))), skia_concurrent_executor_( [runner = concurrent_message_loop_->GetTaskRunner()]( const fml::closure& work) { runner->PostTask(work); }), vm_data_(vm_data), isolate_name_server_(std::move(isolate_name_server)), service_protocol_(std::make_shared()) { TRACE_EVENT0("flutter", "DartVMInitializer"); gVMLaunchCount++; // Setting the executor is not thread safe but Dart VM initialization is. So // this call is thread-safe. SkExecutor::SetDefault(&skia_concurrent_executor_); FML_DCHECK(vm_data_); FML_DCHECK(isolate_name_server_); FML_DCHECK(service_protocol_); { TRACE_EVENT0("flutter", "dart::bin::BootstrapDartIo"); dart::bin::BootstrapDartIo(); if (!settings_.temp_directory_path.empty()) { dart::bin::SetSystemTempDirectory(settings_.temp_directory_path.c_str()); } } std::vector args; // Instruct the VM to ignore unrecognized flags. // There is a lot of diversity in a lot of combinations when it // comes to the arguments the VM supports. And, if the VM comes across a flag // it does not recognize, it exits immediately. args.push_back("--ignore-unrecognized-flags"); for (auto* const profiler_flag : ProfilingFlags(settings_.enable_dart_profiling)) { args.push_back(profiler_flag); } PushBackAll(&args, kDartAllConfigsArgs, std::size(kDartAllConfigsArgs)); if (IsRunningPrecompiledCode()) { PushBackAll(&args, kDartPrecompilationArgs, std::size(kDartPrecompilationArgs)); } // Enable Dart assertions if we are not running precompiled code. We run non- // precompiled code only in the debug product mode. bool enable_asserts = !settings_.disable_dart_asserts; #if !OS_FUCHSIA if (IsRunningPrecompiledCode()) { enable_asserts = false; } #endif // !OS_FUCHSIA #if (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG) #if !FML_OS_IOS && !FML_OS_MACOSX // Debug mode uses the JIT, disable code page write protection to avoid // memory page protection changes before and after every compilation. PushBackAll(&args, kDartWriteProtectCodeArgs, std::size(kDartWriteProtectCodeArgs)); #else const bool tracing_result = EnableTracingIfNecessary(settings_); // This check should only trip if the embedding made no attempts to enable // tracing. At this point, it is too late display user visible messages. Just // log and die. FML_CHECK(tracing_result) << "Tracing not enabled before attempting to run JIT mode VM."; #if TARGET_CPU_ARM // Tell Dart in JIT mode to not use integer division on armv7 // Ideally, this would be detected at runtime by Dart. // TODO(dnfield): Remove this code // https://github.com/dart-lang/sdk/issues/24743 PushBackAll(&args, kDartDisableIntegerDivisionArgs, std::size(kDartDisableIntegerDivisionArgs)); #endif // TARGET_CPU_ARM #endif // !FML_OS_IOS && !FML_OS_MACOSX #endif // (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG) if (enable_asserts) { PushBackAll(&args, kDartAssertArgs, std::size(kDartAssertArgs)); } // On low power devices with lesser number of cores, using concurrent // marking or sweeping causes contention for the UI thread leading to // Jank, this option can be used to turn off all concurrent GC activities. if (settings_.enable_serial_gc) { PushBackAll(&args, kSerialGCArgs, std::size(kSerialGCArgs)); } if (settings_.start_paused) { PushBackAll(&args, kDartStartPausedArgs, std::size(kDartStartPausedArgs)); } if (settings_.endless_trace_buffer || settings_.trace_startup) { // If we are tracing startup, make sure the trace buffer is endless so we // don't lose early traces. PushBackAll(&args, kDartEndlessTraceBufferArgs, std::size(kDartEndlessTraceBufferArgs)); } if (settings_.trace_systrace) { PushBackAll(&args, kDartSystraceTraceBufferArgs, std::size(kDartSystraceTraceBufferArgs)); PushBackAll(&args, kDartSystraceTraceStreamsArgs, std::size(kDartSystraceTraceStreamsArgs)); } std::string file_recorder_args; if (!settings_.trace_to_file.empty()) { file_recorder_args = DartFileRecorderArgs(settings_.trace_to_file); args.push_back(file_recorder_args.c_str()); PushBackAll(&args, kDartSystraceTraceStreamsArgs, std::size(kDartSystraceTraceStreamsArgs)); } if (settings_.trace_startup) { PushBackAll(&args, kDartStartupTraceStreamsArgs, std::size(kDartStartupTraceStreamsArgs)); } #if defined(OS_FUCHSIA) PushBackAll(&args, kDartSystraceTraceBufferArgs, std::size(kDartSystraceTraceBufferArgs)); PushBackAll(&args, kDartSystraceTraceStreamsArgs, std::size(kDartSystraceTraceStreamsArgs)); #else if (!settings_.trace_systrace && !settings_.trace_startup) { PushBackAll(&args, kDartDefaultTraceStreamsArgs, std::size(kDartDefaultTraceStreamsArgs)); } #endif // defined(OS_FUCHSIA) std::string old_gen_heap_size_args; if (settings_.old_gen_heap_size >= 0) { old_gen_heap_size_args = DartOldGenHeapSizeArgs(settings_.old_gen_heap_size); args.push_back(old_gen_heap_size_args.c_str()); } for (size_t i = 0; i < settings_.dart_flags.size(); i++) { args.push_back(settings_.dart_flags[i].c_str()); } char* flags_error = Dart_SetVMFlags(args.size(), args.data()); if (flags_error) { FML_LOG(FATAL) << "Error while setting Dart VM flags: " << flags_error; ::free(flags_error); } dart::bin::SetExecutableName(settings_.executable_name.c_str()); { TRACE_EVENT0("flutter", "Dart_Initialize"); Dart_InitializeParams params = {}; params.version = DART_INITIALIZE_PARAMS_CURRENT_VERSION; params.vm_snapshot_data = vm_data_->GetVMSnapshot().GetDataMapping(); params.vm_snapshot_instructions = vm_data_->GetVMSnapshot().GetInstructionsMapping(); params.create_group = reinterpret_cast( DartIsolate::DartIsolateGroupCreateCallback); params.initialize_isolate = reinterpret_cast( DartIsolate::DartIsolateInitializeCallback); params.shutdown_isolate = reinterpret_cast( DartIsolate::DartIsolateShutdownCallback); params.cleanup_isolate = reinterpret_cast( DartIsolate::DartIsolateCleanupCallback); params.cleanup_group = reinterpret_cast( DartIsolate::DartIsolateGroupCleanupCallback); params.thread_exit = ThreadExitCallback; params.file_open = dart::bin::OpenFile; params.file_read = dart::bin::ReadFile; params.file_write = dart::bin::WriteFile; params.file_close = dart::bin::CloseFile; params.entropy_source = dart::bin::GetEntropy; params.get_service_assets = GetVMServiceAssetsArchiveCallback; DartVMInitializer::Initialize(¶ms, settings_.enable_timeline_event_handler, settings_.trace_systrace); // Send the earliest available timestamp in the application lifecycle to // timeline. The difference between this timestamp and the time we render // the very first frame gives us a good idea about Flutter's startup time. // Use an instant event because the call to Dart_TimelineGetMicros // may behave differently before and after the Dart VM is initialized. // As this call is immediately after initialization of the Dart VM, // we are interested in only one timestamp. int64_t micros = Dart_TimelineGetMicros(); Dart_RecordTimelineEvent("FlutterEngineMainEnter", // label micros, // timestamp0 micros, // timestamp1_or_async_id 0, // flow_id_count nullptr, // flow_ids Dart_Timeline_Event_Instant, // event type 0, // argument_count nullptr, // argument_names nullptr // argument_values ); } Dart_SetFileModifiedCallback(&DartFileModifiedCallback); // Allow streaming of stdout and stderr by the Dart vm. Dart_SetServiceStreamCallbacks(&ServiceStreamListenCallback, &ServiceStreamCancelCallback); Dart_SetEmbedderInformationCallback(&EmbedderInformationCallback); if (settings_.dart_library_sources_kernel != nullptr) { std::unique_ptr dart_library_sources = settings_.dart_library_sources_kernel(); // Set sources for dart:* libraries for debugging. Dart_SetDartLibrarySourcesKernel(dart_library_sources->GetMapping(), dart_library_sources->GetSize()); } // Update thread names now that the Dart VM is initialized. concurrent_message_loop_->PostTaskToAllWorkers( [] { Dart_SetThreadName("FlutterConcurrentMessageLoopWorker"); }); } DartVM::~DartVM() { // Setting the executor is not thread safe but Dart VM shutdown is. So // this call is thread-safe. SkExecutor::SetDefault(nullptr); if (Dart_CurrentIsolate() != nullptr) { Dart_ExitIsolate(); } DartVMInitializer::Cleanup(); dart::bin::CleanupDartIo(); } std::shared_ptr DartVM::GetVMData() const { return vm_data_; } const Settings& DartVM::GetSettings() const { return settings_; } std::shared_ptr DartVM::GetServiceProtocol() const { return service_protocol_; } std::shared_ptr DartVM::GetIsolateNameServer() const { return isolate_name_server_; } std::shared_ptr DartVM::GetConcurrentWorkerTaskRunner() const { return concurrent_message_loop_->GetTaskRunner(); } std::shared_ptr DartVM::GetConcurrentMessageLoop() { return concurrent_message_loop_; } } // namespace flutter