blob: b8b75719b3e4ebbc90f9f3465f8205644c36fee9 [file] [log] [blame]
// 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 <sys/stat.h>
#include <sstream>
#include <vector>
#include "flutter/common/settings.h"
#include "flutter/fml/compiler_specific.h"
#include "flutter/fml/logging.h"
#include "flutter/fml/mapping.h"
#include "flutter/fml/size.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
};
FML_ALLOW_UNUSED_TYPE
static const char* kDartWriteProtectCodeArgs[] = {
"--no_write_protect_code",
};
FML_ALLOW_UNUSED_TYPE
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* kDartDisableServiceAuthCodesArgs[]{
"--disable-service-auth-codes",
};
static const char* kDartEndlessTraceBufferArgs[]{
"--timeline_recorder=endless",
};
// This is the same as --timeline_recorder=systrace.
static const char* kDartSystraceTraceBufferArgs[] = {
"--systrace_timeline",
};
FML_ALLOW_UNUSED_TYPE
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<tonic::Uint8List>::ToDart(mapping.GetMapping(),
mapping.GetSize());
#else
return tonic::DartConverter<tonic::Uint8List>::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<const char*> 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<const char*>* 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> DartVM::Create(
const Settings& settings,
fml::RefPtr<const DartSnapshot> vm_snapshot,
fml::RefPtr<const DartSnapshot> isolate_snapshot,
std::shared_ptr<IsolateNameServer> 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<DartVM>(
new DartVM(vm_data, std::move(isolate_name_server)));
}
static std::atomic_size_t gVMLaunchCount;
size_t DartVM::GetVMLaunchCount() {
return gVMLaunchCount;
}
DartVM::DartVM(const std::shared_ptr<const DartVMData>& vm_data,
std::shared_ptr<IsolateNameServer> isolate_name_server)
: settings_(vm_data->GetSettings()),
concurrent_message_loop_(fml::ConcurrentMessageLoop::Create()),
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<ServiceProtocol>()) {
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<const char*> 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, fml::size(kDartAllConfigsArgs));
if (IsRunningPrecompiledCode()) {
PushBackAll(&args, kDartPrecompilationArgs,
fml::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,
fml::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,
fml::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, fml::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, fml::size(kSerialGCArgs));
}
if (settings_.start_paused) {
PushBackAll(&args, kDartStartPausedArgs, fml::size(kDartStartPausedArgs));
}
if (settings_.disable_service_auth_codes) {
PushBackAll(&args, kDartDisableServiceAuthCodesArgs,
fml::size(kDartDisableServiceAuthCodesArgs));
}
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,
fml::size(kDartEndlessTraceBufferArgs));
}
if (settings_.trace_systrace) {
PushBackAll(&args, kDartSystraceTraceBufferArgs,
fml::size(kDartSystraceTraceBufferArgs));
PushBackAll(&args, kDartSystraceTraceStreamsArgs,
fml::size(kDartSystraceTraceStreamsArgs));
}
if (settings_.trace_startup) {
PushBackAll(&args, kDartStartupTraceStreamsArgs,
fml::size(kDartStartupTraceStreamsArgs));
}
#if defined(OS_FUCHSIA)
PushBackAll(&args, kDartSystraceTraceBufferArgs,
fml::size(kDartSystraceTraceBufferArgs));
PushBackAll(&args, kDartSystraceTraceStreamsArgs,
fml::size(kDartSystraceTraceStreamsArgs));
#else
if (!settings_.trace_systrace && !settings_.trace_startup) {
PushBackAll(&args, kDartDefaultTraceStreamsArgs,
fml::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<decltype(params.create_group)>(
DartIsolate::DartIsolateGroupCreateCallback);
params.initialize_isolate =
reinterpret_cast<decltype(params.initialize_isolate)>(
DartIsolate::DartIsolateInitializeCallback);
params.shutdown_isolate =
reinterpret_cast<decltype(params.shutdown_isolate)>(
DartIsolate::DartIsolateShutdownCallback);
params.cleanup_isolate = reinterpret_cast<decltype(params.cleanup_isolate)>(
DartIsolate::DartIsolateCleanupCallback);
params.cleanup_group = reinterpret_cast<decltype(params.cleanup_group)>(
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(&params,
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_TimelineEvent("FlutterEngineMainEnter", // label
micros, // timestamp0
micros, // timestamp1_or_async_id
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<fml::Mapping> 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<const DartVMData> DartVM::GetVMData() const {
return vm_data_;
}
const Settings& DartVM::GetSettings() const {
return settings_;
}
std::shared_ptr<ServiceProtocol> DartVM::GetServiceProtocol() const {
return service_protocol_;
}
std::shared_ptr<IsolateNameServer> DartVM::GetIsolateNameServer() const {
return isolate_name_server_;
}
std::shared_ptr<fml::ConcurrentTaskRunner>
DartVM::GetConcurrentWorkerTaskRunner() const {
return concurrent_message_loop_->GetTaskRunner();
}
std::shared_ptr<fml::ConcurrentMessageLoop> DartVM::GetConcurrentMessageLoop() {
return concurrent_message_loop_;
}
} // namespace flutter