From d96f962ca21a104b08836f34814eb6b267937511 Mon Sep 17 00:00:00 2001 From: Marcus Tomlinson Date: Sun, 10 May 2020 18:24:56 +0100 Subject: [PATCH] Add new FlutterEngineAOTData argument to FlutterProjectArgs (#18146) Added a new `FlutterEngineAOTData` argument to `FlutterProjectArgs`. Embedders can instantiate and destroy this object via the new `FlutterEngineCreateAOTData` and `FlutterEngineCollectAOTData` methods provided. If an embedder provides more than one source of AOT data to `FlutterEngineInitialize` or `FlutterEngineRun` (e.g. snapshots as well as `FlutterEngineAOTData`), the engine will error out. Resolves: https://github.com/flutter/flutter/issues/50778 --- shell/platform/embedder/BUILD.gn | 1 + shell/platform/embedder/embedder.cc | 104 +++++++++++++++++ shell/platform/embedder/embedder.h | 59 ++++++++++ .../embedder/tests/embedder_config_builder.cc | 17 ++- .../embedder/tests/embedder_config_builder.h | 8 +- .../embedder/tests/embedder_test_context.cc | 24 ++++ .../embedder/tests/embedder_test_context.h | 15 +++ .../embedder/tests/embedder_unittests.cc | 109 ++++++++++++++++++ testing/elf_loader.cc | 2 - testing/elf_loader.h | 2 + 10 files changed, 335 insertions(+), 6 deletions(-) diff --git a/shell/platform/embedder/BUILD.gn b/shell/platform/embedder/BUILD.gn index 09547a3de..f4b5be57e 100644 --- a/shell/platform/embedder/BUILD.gn +++ b/shell/platform/embedder/BUILD.gn @@ -71,6 +71,7 @@ template("embedder_source_set") { "//flutter/shell/common", "//flutter/third_party/tonic", "//third_party/dart/runtime/bin:dart_io_api", + "//third_party/dart/runtime/bin:elf_loader", "//third_party/skia", ] diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index 102b4f445..bfbcb0033 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -11,6 +11,7 @@ #include "flutter/fml/closure.h" #include "flutter/fml/make_copyable.h" #include "flutter/fml/native_library.h" +#include "third_party/dart/runtime/bin/elf_loader.h" #include "third_party/dart/runtime/include/dart_native_api.h" #if OS_WIN @@ -532,6 +533,83 @@ struct _FlutterPlatformMessageResponseHandle { fml::RefPtr message; }; +struct LoadedElfDeleter { + void operator()(Dart_LoadedElf* elf) { + if (elf) { + ::Dart_UnloadELF(elf); + } + } +}; + +using UniqueLoadedElf = std::unique_ptr; + +struct _FlutterEngineAOTData { + UniqueLoadedElf loaded_elf = nullptr; + const uint8_t* vm_snapshot_data = nullptr; + const uint8_t* vm_snapshot_instrs = nullptr; + const uint8_t* vm_isolate_data = nullptr; + const uint8_t* vm_isolate_instrs = nullptr; +}; + +FlutterEngineResult FlutterEngineCreateAOTData( + const FlutterEngineAOTDataSource* source, + FlutterEngineAOTData* data_out) { + if (!flutter::DartVM::IsRunningPrecompiledCode()) { + return LOG_EMBEDDER_ERROR(kInvalidArguments, + "AOT data can only be created in AOT mode."); + } else if (!source) { + return LOG_EMBEDDER_ERROR(kInvalidArguments, "Null source specified."); + } else if (!data_out) { + return LOG_EMBEDDER_ERROR(kInvalidArguments, "Null data_out specified."); + } + + switch (source->type) { + case kFlutterEngineAOTDataSourceTypeElfPath: { + if (!source->elf_path || !fml::IsFile(source->elf_path)) { + return LOG_EMBEDDER_ERROR(kInvalidArguments, + "Invalid ELF path specified."); + } + + auto aot_data = std::make_unique<_FlutterEngineAOTData>(); + const char* error = nullptr; + + Dart_LoadedElf* loaded_elf = Dart_LoadELF( + source->elf_path, // file path + 0, // file offset + &error, // error (out) + &aot_data->vm_snapshot_data, // vm snapshot data (out) + &aot_data->vm_snapshot_instrs, // vm snapshot instr (out) + &aot_data->vm_isolate_data, // vm isolate data (out) + &aot_data->vm_isolate_instrs // vm isolate instr (out) + ); + + if (loaded_elf == nullptr) { + return LOG_EMBEDDER_ERROR(kInvalidArguments, error); + } + + aot_data->loaded_elf.reset(loaded_elf); + + *data_out = aot_data.release(); + return kSuccess; + } + } + + return LOG_EMBEDDER_ERROR( + kInvalidArguments, + "Invalid FlutterEngineAOTDataSourceType type specified."); +} + +FlutterEngineResult FlutterEngineCollectAOTData(FlutterEngineAOTData data) { + if (data) { + data->loaded_elf = nullptr; + data->vm_snapshot_data = nullptr; + data->vm_snapshot_instrs = nullptr; + data->vm_isolate_data = nullptr; + data->vm_isolate_instrs = nullptr; + } + return kSuccess; +} + void PopulateSnapshotMappingCallbacks(const FlutterProjectArgs* args, flutter::Settings& settings) { // There are no ownership concerns here as all mappings are owned by the @@ -543,6 +621,20 @@ void PopulateSnapshotMappingCallbacks(const FlutterProjectArgs* args, }; if (flutter::DartVM::IsRunningPrecompiledCode()) { + if (SAFE_ACCESS(args, aot_data, nullptr) != nullptr) { + settings.vm_snapshot_data = + make_mapping_callback(args->aot_data->vm_snapshot_data, 0); + + settings.vm_snapshot_instr = + make_mapping_callback(args->aot_data->vm_snapshot_instrs, 0); + + settings.isolate_snapshot_data = + make_mapping_callback(args->aot_data->vm_isolate_data, 0); + + settings.isolate_snapshot_instr = + make_mapping_callback(args->aot_data->vm_isolate_instrs, 0); + } + if (SAFE_ACCESS(args, vm_snapshot_data, nullptr) != nullptr) { settings.vm_snapshot_data = make_mapping_callback( args->vm_snapshot_data, SAFE_ACCESS(args, vm_snapshot_data_size, 0)); @@ -659,6 +751,18 @@ FlutterEngineResult FlutterEngineInitialize(size_t version, flutter::Settings settings = flutter::SettingsFromCommandLine(command_line); + if (SAFE_ACCESS(args, aot_data, nullptr)) { + if (SAFE_ACCESS(args, vm_snapshot_data, nullptr) || + SAFE_ACCESS(args, vm_snapshot_instructions, nullptr) || + SAFE_ACCESS(args, isolate_snapshot_data, nullptr) || + SAFE_ACCESS(args, isolate_snapshot_instructions, nullptr)) { + return LOG_EMBEDDER_ERROR( + kInvalidArguments, + "Multiple AOT sources specified. Embedders should provide either " + "*_snapshot_* buffers or aot_data, not both."); + } + } + PopulateSnapshotMappingCallbacks(args, settings); settings.icu_data_path = icu_data_path; diff --git a/shell/platform/embedder/embedder.h b/shell/platform/embedder/embedder.h index cab034129..67c5437ed 100644 --- a/shell/platform/embedder/embedder.h +++ b/shell/platform/embedder/embedder.h @@ -959,6 +959,57 @@ typedef enum { typedef void (*FlutterNativeThreadCallback)(FlutterNativeThreadType type, void* user_data); +/// AOT data source type. +typedef enum { + kFlutterEngineAOTDataSourceTypeElfPath +} FlutterEngineAOTDataSourceType; + +/// This struct specifies one of the various locations the engine can look for +/// AOT data sources. +typedef struct { + FlutterEngineAOTDataSourceType type; + union { + /// Absolute path to an ELF library file. + const char* elf_path; + }; +} FlutterEngineAOTDataSource; + +/// An opaque object that describes the AOT data that can be used to launch a +/// FlutterEngine instance in AOT mode. +typedef struct _FlutterEngineAOTData* FlutterEngineAOTData; + +//------------------------------------------------------------------------------ +/// @brief Creates the necessary data structures to launch a Flutter Dart +/// application in AOT mode. The data may only be collected after +/// all FlutterEngine instances launched using this data have been +/// terminated. +/// +/// @param[in] source The source of the AOT data. +/// @param[out] data_out The AOT data on success. Unchanged on failure. +/// +/// @return Returns if the AOT data could be successfully resolved. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineCreateAOTData( + const FlutterEngineAOTDataSource* source, + FlutterEngineAOTData* data_out); + +//------------------------------------------------------------------------------ +/// @brief Collects the AOT data. +/// +/// @warning The embedder must ensure that this call is made only after all +/// FlutterEngine instances launched using this data have been +/// terminated, and that all of those instances were launched with +/// the FlutterProjectArgs::shutdown_dart_vm_when_done flag set to +/// true. +/// +/// @param[in] data The data to collect. +/// +/// @return Returns if the AOT data was successfully collected. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineCollectAOTData(FlutterEngineAOTData data); + typedef struct { /// The size of this struct. Must be sizeof(FlutterProjectArgs). size_t struct_size; @@ -1146,6 +1197,14 @@ typedef struct { /// See also: /// https://github.com/dart-lang/sdk/blob/ca64509108b3e7219c50d6c52877c85ab6a35ff2/runtime/vm/flag_list.h#L150 int64_t dart_old_gen_heap_size; + + /// The AOT data to be used in AOT operation. + /// + /// Embedders should instantiate and destroy this object via the + /// FlutterEngineCreateAOTData and FlutterEngineCollectAOTData methods. + /// + /// Embedders can provide either snapshot buffers or aot_data, but not both. + FlutterEngineAOTData aot_data; } FlutterProjectArgs; //------------------------------------------------------------------------------ diff --git a/shell/platform/embedder/tests/embedder_config_builder.cc b/shell/platform/embedder/tests/embedder_config_builder.cc index 567729a21..72cc3d400 100644 --- a/shell/platform/embedder/tests/embedder_config_builder.cc +++ b/shell/platform/embedder/tests/embedder_config_builder.cc @@ -4,6 +4,7 @@ #include "flutter/shell/platform/embedder/tests/embedder_config_builder.h" +#include "flutter/runtime/dart_vm.h" #include "flutter/shell/platform/embedder/embedder.h" #include "third_party/skia/include/core/SkBitmap.h" @@ -75,12 +76,20 @@ EmbedderConfigBuilder::EmbedderConfigBuilder( // to do this manually. AddCommandLineArgument("embedder_unittest"); - if (preference == InitializationPreference::kInitialize) { + if (preference != InitializationPreference::kNoInitialize) { SetAssetsPath(); - SetSnapshots(); SetIsolateCreateCallbackHook(); SetSemanticsCallbackHooks(); AddCommandLineArgument("--disable-observatory"); + + if (preference == InitializationPreference::kSnapshotsInitialize || + preference == InitializationPreference::kMultiAOTInitialize) { + SetSnapshots(); + } + if (preference == InitializationPreference::kAOTDataInitialize || + preference == InitializationPreference::kMultiAOTInitialize) { + SetAOTDataElf(); + } } } @@ -132,6 +141,10 @@ void EmbedderConfigBuilder::SetSnapshots() { } } +void EmbedderConfigBuilder::SetAOTDataElf() { + project_args_.aot_data = context_.GetAOTData(); +} + void EmbedderConfigBuilder::SetIsolateCreateCallbackHook() { project_args_.root_isolate_create_callback = EmbedderTestContext::GetIsolateCreateCallbackHook(); diff --git a/shell/platform/embedder/tests/embedder_config_builder.h b/shell/platform/embedder/tests/embedder_config_builder.h index 1dfdd7f07..6512f1524 100644 --- a/shell/platform/embedder/tests/embedder_config_builder.h +++ b/shell/platform/embedder/tests/embedder_config_builder.h @@ -31,13 +31,15 @@ using UniqueEngine = fml::UniqueObject; class EmbedderConfigBuilder { public: enum class InitializationPreference { - kInitialize, + kSnapshotsInitialize, + kAOTDataInitialize, + kMultiAOTInitialize, kNoInitialize, }; EmbedderConfigBuilder(EmbedderTestContext& context, InitializationPreference preference = - InitializationPreference::kInitialize); + InitializationPreference::kSnapshotsInitialize); ~EmbedderConfigBuilder(); @@ -51,6 +53,8 @@ class EmbedderConfigBuilder { void SetSnapshots(); + void SetAOTDataElf(); + void SetIsolateCreateCallbackHook(); void SetSemanticsCallbackHooks(); diff --git a/shell/platform/embedder/tests/embedder_test_context.cc b/shell/platform/embedder/tests/embedder_test_context.cc index f0cdd8850..bb2c71788 100644 --- a/shell/platform/embedder/tests/embedder_test_context.cc +++ b/shell/platform/embedder/tests/embedder_test_context.cc @@ -8,6 +8,7 @@ #include "flutter/fml/paths.h" #include "flutter/runtime/dart_vm.h" #include "flutter/shell/platform/embedder/tests/embedder_assertions.h" +#include "flutter/testing/testing.h" #include "third_party/dart/runtime/bin/elf_loader.h" #include "third_party/skia/include/core/SkSurface.h" @@ -19,6 +20,7 @@ EmbedderTestContext::EmbedderTestContext(std::string assets_path) aot_symbols_(LoadELFSymbolFromFixturesIfNeccessary()), native_resolver_(std::make_shared()) { SetupAOTMappingsIfNecessary(); + SetupAOTDataIfNecessary(); isolate_create_callbacks_.push_back( [weak_resolver = std::weak_ptr{native_resolver_}]() { @@ -44,6 +46,24 @@ void EmbedderTestContext::SetupAOTMappingsIfNecessary() { aot_symbols_.vm_isolate_instrs, 0u); } +void EmbedderTestContext::SetupAOTDataIfNecessary() { + if (!DartVM::IsRunningPrecompiledCode()) { + return; + } + FlutterEngineAOTDataSource data_in = {}; + FlutterEngineAOTData data_out = nullptr; + + const auto elf_path = + fml::paths::JoinPaths({GetFixturesPath(), kAOTAppELFFileName}); + + data_in.type = kFlutterEngineAOTDataSourceTypeElfPath; + data_in.elf_path = elf_path.c_str(); + + ASSERT_EQ(FlutterEngineCreateAOTData(&data_in, &data_out), kSuccess); + + aot_data_.reset(data_out); +} + const std::string& EmbedderTestContext::GetAssetsPath() const { return assets_path_; } @@ -65,6 +85,10 @@ const fml::Mapping* EmbedderTestContext::GetIsolateSnapshotInstructions() return isolate_snapshot_instructions_.get(); } +FlutterEngineAOTData EmbedderTestContext::GetAOTData() const { + return aot_data_.get(); +} + void EmbedderTestContext::SetRootSurfaceTransformation(SkMatrix matrix) { root_surface_transformation_ = matrix; } diff --git a/shell/platform/embedder/tests/embedder_test_context.h b/shell/platform/embedder/tests/embedder_test_context.h index fc7bf59b7..182edcd31 100644 --- a/shell/platform/embedder/tests/embedder_test_context.h +++ b/shell/platform/embedder/tests/embedder_test_context.h @@ -28,6 +28,16 @@ using SemanticsNodeCallback = std::function; using SemanticsActionCallback = std::function; +struct AOTDataDeleter { + void operator()(FlutterEngineAOTData aot_data) { + if (aot_data) { + FlutterEngineCollectAOTData(aot_data); + } + } +}; + +using UniqueAOTData = std::unique_ptr<_FlutterEngineAOTData, AOTDataDeleter>; + class EmbedderTestContext { public: EmbedderTestContext(std::string assets_path = ""); @@ -44,6 +54,8 @@ class EmbedderTestContext { const fml::Mapping* GetIsolateSnapshotInstructions() const; + FlutterEngineAOTData GetAOTData() const; + void SetRootSurfaceTransformation(SkMatrix matrix); void AddIsolateCreateCallback(fml::closure closure); @@ -79,6 +91,7 @@ class EmbedderTestContext { std::unique_ptr vm_snapshot_instructions_; std::unique_ptr isolate_snapshot_data_; std::unique_ptr isolate_snapshot_instructions_; + UniqueAOTData aot_data_; std::vector isolate_create_callbacks_; std::shared_ptr native_resolver_; SemanticsNodeCallback update_semantics_node_callback_; @@ -101,6 +114,8 @@ class EmbedderTestContext { void SetupAOTMappingsIfNecessary(); + void SetupAOTDataIfNecessary(); + void SetupCompositor(); void FireIsolateCreateCallbacks(); diff --git a/shell/platform/embedder/tests/embedder_unittests.cc b/shell/platform/embedder/tests/embedder_unittests.cc index 78286fcf5..82e392bbf 100644 --- a/shell/platform/embedder/tests/embedder_unittests.cc +++ b/shell/platform/embedder/tests/embedder_unittests.cc @@ -4207,5 +4207,114 @@ TEST_F(EmbedderTest, CompositorRenderTargetsAreInStableOrder) { latch.Wait(); } +TEST_F(EmbedderTest, InvalidAOTDataSourcesMustReturnError) { + if (!DartVM::IsRunningPrecompiledCode()) { + GTEST_SKIP(); + return; + } + FlutterEngineAOTDataSource data_in = {}; + FlutterEngineAOTData data_out = nullptr; + + // Null source specified. + ASSERT_EQ(FlutterEngineCreateAOTData(nullptr, &data_out), kInvalidArguments); + ASSERT_EQ(data_out, nullptr); + + // Null data_out specified. + ASSERT_EQ(FlutterEngineCreateAOTData(&data_in, nullptr), kInvalidArguments); + + // Invalid FlutterEngineAOTDataSourceType type specified. + data_in.type = FlutterEngineAOTDataSourceType(-1); + ASSERT_EQ(FlutterEngineCreateAOTData(&data_in, &data_out), kInvalidArguments); + ASSERT_EQ(data_out, nullptr); + + // Invalid ELF path specified. + data_in.type = kFlutterEngineAOTDataSourceTypeElfPath; + data_in.elf_path = nullptr; + ASSERT_EQ(FlutterEngineCreateAOTData(&data_in, &data_out), kInvalidArguments); + ASSERT_EQ(data_in.type, kFlutterEngineAOTDataSourceTypeElfPath); + ASSERT_EQ(data_in.elf_path, nullptr); + ASSERT_EQ(data_out, nullptr); + + // Invalid ELF path specified. + data_in.elf_path = ""; + ASSERT_EQ(FlutterEngineCreateAOTData(&data_in, &data_out), kInvalidArguments); + ASSERT_EQ(data_in.type, kFlutterEngineAOTDataSourceTypeElfPath); + ASSERT_EQ(data_in.elf_path, ""); + ASSERT_EQ(data_out, nullptr); + + // Could not find VM snapshot data. + data_in.elf_path = "/bin/true"; + ASSERT_EQ(FlutterEngineCreateAOTData(&data_in, &data_out), kInvalidArguments); + ASSERT_EQ(data_in.type, kFlutterEngineAOTDataSourceTypeElfPath); + ASSERT_EQ(data_in.elf_path, "/bin/true"); + ASSERT_EQ(data_out, nullptr); +} + +TEST_F(EmbedderTest, MustNotRunWithMultipleAOTSources) { + if (!DartVM::IsRunningPrecompiledCode()) { + GTEST_SKIP(); + return; + } + auto& context = GetEmbedderContext(); + + EmbedderConfigBuilder builder( + context, + EmbedderConfigBuilder::InitializationPreference::kMultiAOTInitialize); + + builder.SetSoftwareRendererConfig(); + + auto engine = builder.LaunchEngine(); + ASSERT_FALSE(engine.is_valid()); +} + +TEST_F(EmbedderTest, CanCreateAndCollectAValidElfSource) { + if (!DartVM::IsRunningPrecompiledCode()) { + GTEST_SKIP(); + return; + } + FlutterEngineAOTDataSource data_in = {}; + FlutterEngineAOTData data_out = nullptr; + + // Collecting a null object should be allowed + ASSERT_EQ(FlutterEngineCollectAOTData(data_out), kSuccess); + + const auto elf_path = + fml::paths::JoinPaths({GetFixturesPath(), kAOTAppELFFileName}); + + data_in.type = kFlutterEngineAOTDataSourceTypeElfPath; + data_in.elf_path = elf_path.c_str(); + + ASSERT_EQ(FlutterEngineCreateAOTData(&data_in, &data_out), kSuccess); + ASSERT_EQ(data_in.type, kFlutterEngineAOTDataSourceTypeElfPath); + ASSERT_EQ(data_in.elf_path, elf_path.c_str()); + ASSERT_NE(data_out, nullptr); + + ASSERT_EQ(FlutterEngineCollectAOTData(data_out), kSuccess); +} + +TEST_F(EmbedderTest, CanLaunchAndShutdownWithAValidElfSource) { + if (!DartVM::IsRunningPrecompiledCode()) { + GTEST_SKIP(); + return; + } + auto& context = GetEmbedderContext(); + + fml::AutoResetWaitableEvent latch; + context.AddIsolateCreateCallback([&latch]() { latch.Signal(); }); + + EmbedderConfigBuilder builder( + context, + EmbedderConfigBuilder::InitializationPreference::kAOTDataInitialize); + + builder.SetSoftwareRendererConfig(); + + auto engine = builder.LaunchEngine(); + ASSERT_TRUE(engine.is_valid()); + + // Wait for the root isolate to launch. + latch.Wait(); + engine.reset(); +} + } // namespace testing } // namespace flutter diff --git a/testing/elf_loader.cc b/testing/elf_loader.cc index 9b1eab139..618859d00 100644 --- a/testing/elf_loader.cc +++ b/testing/elf_loader.cc @@ -12,8 +12,6 @@ namespace flutter { namespace testing { -static constexpr const char* kAOTAppELFFileName = "app_elf_snapshot.so"; - ELFAOTSymbols LoadELFSymbolFromFixturesIfNeccessary() { if (!DartVM::IsRunningPrecompiledCode()) { return {}; diff --git a/testing/elf_loader.h b/testing/elf_loader.h index 1d9a93de6..a8c37c921 100644 --- a/testing/elf_loader.h +++ b/testing/elf_loader.h @@ -14,6 +14,8 @@ namespace flutter { namespace testing { +inline constexpr const char* kAOTAppELFFileName = "app_elf_snapshot.so"; + struct LoadedELFDeleter { void operator()(Dart_LoadedElf* elf) { ::Dart_UnloadELF(elf); } }; -- GitLab