From 903ef0892fcc2e8e87eeceb2fab62fd8e1a4d975 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Tue, 26 May 2020 11:01:48 -0700 Subject: [PATCH] Support AOT mode for Windows (#18589) - Adds a way to provide an AOT library to the C API. - Adds app.so to the information provided by DartProject. - Fixes the engine to only do static snapshot linking for Windows in debug mode, not all modes, so that the provided library is used. Engine side of https://github.com/flutter/flutter/issues/38477 --- runtime/BUILD.gn | 22 -------- runtime/dart_snapshot.cc | 2 +- .../client_wrapper/dart_project_unittests.cc | 6 +++ .../client_wrapper/flutter_view_controller.cc | 2 + .../include/flutter/dart_project.h | 6 +++ shell/platform/windows/flutter_windows.cc | 50 ++++++++++++++++++- .../platform/windows/public/flutter_windows.h | 9 +++- shell/platform/windows/window_state.h | 11 ++++ 8 files changed, 82 insertions(+), 26 deletions(-) diff --git a/runtime/BUILD.gn b/runtime/BUILD.gn index 8dd56e94525..0ee1eed0970 100644 --- a/runtime/BUILD.gn +++ b/runtime/BUILD.gn @@ -89,28 +89,6 @@ source_set("runtime") { "//third_party/skia", ] - # On Windows the engine finds the Dart snapshot data through symbols - # that are statically linked into the executable. On other platforms this - # data is obtained by a dynamic symbol lookup. - # The current Windows platform uses static-link instead of dynamic lookups. - # Because: - # 1. The size of the executable on Windows is not as concerned as on mobile - # platforms. - # 2. If it is a dynamic lookup method, you need to modify the properties of - # the memory page after the *.bin is dynamically loaded at runtime and - # mark it as executable. This is feasible (similar to V8 on Windows), but - # this increases the complexity of the implementation(These operations - # involve the use of some system file/memory privilege APIs and there is - # a risk of failure). - # 3. In the executable can enjoy the advantages of the windows preload - # mechanism, speed up I/O. - if (is_win) { - if (flutter_runtime_mode == "profile" || - flutter_runtime_mode == "release") { - deps += [ "//flutter/lib/snapshot" ] - } - } - public_deps = [ "//third_party/rapidjson", ] diff --git a/runtime/dart_snapshot.cc b/runtime/dart_snapshot.cc index 0a19b3984ca..050592d04a9 100644 --- a/runtime/dart_snapshot.cc +++ b/runtime/dart_snapshot.cc @@ -24,7 +24,7 @@ const char* DartSnapshot::kIsolateInstructionsSymbol = // data through symbols that are statically linked into the executable. // On other platforms this data is obtained by a dynamic symbol lookup. #define DART_SNAPSHOT_STATIC_LINK \ - (OS_WIN || (OS_ANDROID && FLUTTER_JIT_RUNTIME)) + ((OS_WIN || OS_ANDROID) && FLUTTER_JIT_RUNTIME) #if !DART_SNAPSHOT_STATIC_LINK diff --git a/shell/platform/windows/client_wrapper/dart_project_unittests.cc b/shell/platform/windows/client_wrapper/dart_project_unittests.cc index e6d6c17430e..7be4593e0a3 100644 --- a/shell/platform/windows/client_wrapper/dart_project_unittests.cc +++ b/shell/platform/windows/client_wrapper/dart_project_unittests.cc @@ -22,6 +22,11 @@ class DartProjectTest : public ::testing::Test { return project.assets_path(); } + // Wrapper for accessing private aot_library_path_. + std::wstring GetProjectAotLibraryPath(const DartProject& project) { + return project.aot_library_path(); + } + // Wrapper for accessing private engine_switches. std::vector GetProjectEngineSwitches( const DartProject& project) { @@ -33,6 +38,7 @@ TEST_F(DartProjectTest, StandardProjectFormat) { DartProject project(L"test"); EXPECT_EQ(GetProjectIcuDataPath(project), L"test\\icudtl.dat"); EXPECT_EQ(GetProjectAssetsPath(project), L"test\\flutter_assets"); + EXPECT_EQ(GetProjectAotLibraryPath(project), L"test\\app.so"); } TEST_F(DartProjectTest, Switches) { diff --git a/shell/platform/windows/client_wrapper/flutter_view_controller.cc b/shell/platform/windows/client_wrapper/flutter_view_controller.cc index a16a0699dd1..16dd49f8a24 100644 --- a/shell/platform/windows/client_wrapper/flutter_view_controller.cc +++ b/shell/platform/windows/client_wrapper/flutter_view_controller.cc @@ -22,6 +22,8 @@ FlutterViewController::FlutterViewController(int width, FlutterDesktopEngineProperties properties = {}; properties.assets_path = project.assets_path().c_str(); properties.icu_data_path = project.icu_data_path().c_str(); + // It is harmless to pass this in non-AOT mode. + properties.aot_library_path = project.aot_library_path().c_str(); properties.switches = switch_count > 0 ? switches.data() : nullptr; properties.switches_count = switch_count; controller_ = FlutterDesktopCreateViewController(width, height, properties); diff --git a/shell/platform/windows/client_wrapper/include/flutter/dart_project.h b/shell/platform/windows/client_wrapper/include/flutter/dart_project.h index 3595239ebd5..ae542aebf1d 100644 --- a/shell/platform/windows/client_wrapper/include/flutter/dart_project.h +++ b/shell/platform/windows/client_wrapper/include/flutter/dart_project.h @@ -17,12 +17,14 @@ class DartProject { // the following top-level items: // - icudtl.dat (provided as a resource by the Flutter tool) // - flutter_assets (as built by the Flutter tool) + // - app.so, for an AOT build (as built by the Flutter tool) // // The path can either be absolute, or relative to the directory containing // the running executable. explicit DartProject(const std::wstring& path) { assets_path_ = path + L"\\flutter_assets"; icu_data_path_ = path + L"\\icudtl.dat"; + aot_library_path_ = path + L"\\app.so"; } ~DartProject() = default; @@ -48,6 +50,7 @@ class DartProject { const std::wstring& assets_path() const { return assets_path_; } const std::wstring& icu_data_path() const { return icu_data_path_; } + const std::wstring& aot_library_path() const { return aot_library_path_; } const std::vector& engine_switches() const { return engine_switches_; } @@ -56,6 +59,9 @@ class DartProject { std::wstring assets_path_; // The path to the ICU data. std::wstring icu_data_path_; + // The path to the AOT library. This will always return a path, but non-AOT + // builds will not be expected to actually have a library at that path. + std::wstring aot_library_path_; // Switches to pass to the engine. std::vector engine_switches_; }; diff --git a/shell/platform/windows/flutter_windows.cc b/shell/platform/windows/flutter_windows.cc index 805ca6cd31a..52319db72d5 100644 --- a/shell/platform/windows/flutter_windows.cc +++ b/shell/platform/windows/flutter_windows.cc @@ -12,6 +12,7 @@ #include #include #include +#include #include #include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/plugin_registrar.h" @@ -29,6 +30,33 @@ static_assert(FLUTTER_ENGINE_VERSION == 1, ""); +// Attempts to load AOT data from the given path, which must be absolute and +// non-empty. Logs and returns nullptr on failure. +UniqueAotDataPtr LoadAotData(std::filesystem::path aot_data_path) { + if (aot_data_path.empty()) { + std::cerr + << "Attempted to load AOT data, but no aot_library_path was provided." + << std::endl; + return nullptr; + } + if (!std::filesystem::exists(aot_data_path)) { + std::cerr << "Can't load AOT data from " << aot_data_path.u8string() + << "; no such file." << std::endl; + return nullptr; + } + std::string path_string = aot_data_path.u8string(); + FlutterEngineAOTDataSource source = {}; + source.type = kFlutterEngineAOTDataSourceTypeElfPath; + source.elf_path = path_string.c_str(); + FlutterEngineAOTData data = nullptr; + auto result = FlutterEngineCreateAOTData(&source, &data); + if (result != kSuccess) { + std::cerr << "Failed to load AOT data from: " << path_string << std::endl; + return nullptr; + } + return UniqueAotDataPtr(data); +} + // Spins up an instance of the Flutter Engine. // // This function launches the Flutter Engine in a background thread, supplying @@ -108,7 +136,12 @@ static std::unique_ptr RunFlutterEngine( std::filesystem::path assets_path(engine_properties.assets_path); std::filesystem::path icu_path(engine_properties.icu_data_path); - if (assets_path.is_relative() || icu_path.is_relative()) { + std::filesystem::path aot_library_path = + engine_properties.aot_library_path == nullptr + ? std::filesystem::path() + : std::filesystem::path(engine_properties.aot_library_path); + if (assets_path.is_relative() || icu_path.is_relative() || + (!aot_library_path.empty() && aot_library_path.is_relative())) { // Treat relative paths as relative to the directory of this executable. std::filesystem::path executable_location = flutter::GetExecutableDirectory(); @@ -120,10 +153,22 @@ static std::unique_ptr RunFlutterEngine( } assets_path = std::filesystem::path(executable_location) / assets_path; icu_path = std::filesystem::path(executable_location) / icu_path; + if (!aot_library_path.empty()) { + aot_library_path = + std::filesystem::path(executable_location) / aot_library_path; + } } std::string assets_path_string = assets_path.u8string(); std::string icu_path_string = icu_path.u8string(); + if (FlutterEngineRunsAOTCompiledDartCode()) { + state->aot_data = LoadAotData(aot_library_path); + if (!state->aot_data) { + std::cerr << "Unable to start engine without AOT data." << std::endl; + return nullptr; + } + } + FlutterProjectArgs args = {}; args.struct_size = sizeof(FlutterProjectArgs); args.assets_path = assets_path_string.c_str(); @@ -137,6 +182,9 @@ static std::unique_ptr RunFlutterEngine( return window->HandlePlatformMessage(engine_message); }; args.custom_task_runners = &custom_task_runners; + if (state->aot_data) { + args.aot_data = state->aot_data.get(); + } FLUTTER_API_SYMBOL(FlutterEngine) engine = nullptr; auto result = diff --git a/shell/platform/windows/public/flutter_windows.h b/shell/platform/windows/public/flutter_windows.h index 633169a4cf4..5915da7b32c 100644 --- a/shell/platform/windows/public/flutter_windows.h +++ b/shell/platform/windows/public/flutter_windows.h @@ -5,6 +5,7 @@ #ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_PUBLIC_FLUTTER_H_ #define FLUTTER_SHELL_PLATFORM_WINDOWS_PUBLIC_FLUTTER_H_ +#include #include #include @@ -12,8 +13,6 @@ #include "flutter_messenger.h" #include "flutter_plugin_registrar.h" -#include "Windows.h" - #if defined(__cplusplus) extern "C" { #endif @@ -40,6 +39,12 @@ typedef struct { // containing the executable. const wchar_t* icu_data_path; + // The path to the AOT libary file for your application, if any. + // This can either be an absolute path or a path relative to the directory + // containing the executable. This can be nullptr for a non-AOT build, as + // it will be ignored in that case. + const wchar_t* aot_library_path; + // The switches to pass to the Flutter engine. // // See: https://github.com/flutter/engine/blob/master/shell/common/switches.h diff --git a/shell/platform/windows/window_state.h b/shell/platform/windows/window_state.h index 6f0451d7ec9..61721fa1f21 100644 --- a/shell/platform/windows/window_state.h +++ b/shell/platform/windows/window_state.h @@ -39,6 +39,14 @@ struct FlutterDesktopView { flutter::Win32FlutterWindow* window; }; +struct AotDataDeleter { + void operator()(FlutterEngineAOTData aot_data) { + FlutterEngineCollectAOTData(aot_data); + } +}; + +using UniqueAotDataPtr = std::unique_ptr<_FlutterEngineAOTData, AotDataDeleter>; + // Struct for storing state of a Flutter engine instance. struct FlutterDesktopEngineState { // The handle to the Flutter engine instance. @@ -46,6 +54,9 @@ struct FlutterDesktopEngineState { // Task runner for tasks posted from the engine. std::unique_ptr task_runner; + + // AOT data, if any. + UniqueAotDataPtr aot_data; }; // State associated with the plugin registrar.