diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index bc681ffb3ba48..e5e9ba58e321f 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -590,6 +590,7 @@ FILE: ../../../flutter/runtime/dart_vm_unittests.cc FILE: ../../../flutter/runtime/embedder_resources.cc FILE: ../../../flutter/runtime/embedder_resources.h FILE: ../../../flutter/runtime/fixtures/runtime_test.dart +FILE: ../../../flutter/runtime/fixtures/split_lib_test.dart FILE: ../../../flutter/runtime/isolate_configuration.cc FILE: ../../../flutter/runtime/isolate_configuration.h FILE: ../../../flutter/runtime/platform_data.cc diff --git a/lib/ui/window/platform_configuration.h b/lib/ui/window/platform_configuration.h index 693ec18bcbd95..cd22a965b255a 100644 --- a/lib/ui/window/platform_configuration.h +++ b/lib/ui/window/platform_configuration.h @@ -174,6 +174,27 @@ class PlatformConfigurationClient { ComputePlatformResolvedLocale( const std::vector& supported_locale_data) = 0; + //-------------------------------------------------------------------------- + /// @brief Invoked when the Dart VM requests that a deferred library + /// be loaded. Notifies the engine that the deferred library + /// identified by the specified loading unit id should be + /// downloaded and loaded into the Dart VM via + /// `LoadDartDeferredLibrary` + /// + /// Upon encountering errors or otherwise failing to load a + /// loading unit with the specified id, the failure should be + /// directly reported to dart by calling + /// `LoadDartDeferredLibraryFailure` to ensure the waiting dart + /// future completes with an error. + /// + /// @param[in] loading_unit_id The unique id of the deferred library's + /// loading unit. This id is to be passed + /// back into LoadDartDeferredLibrary + /// in order to identify which deferred + /// library to load. + /// + virtual void RequestDartDeferredLibrary(intptr_t loading_unit_id) = 0; + protected: virtual ~PlatformConfigurationClient(); }; diff --git a/runtime/dart_isolate.cc b/runtime/dart_isolate.cc index 8a6215110e426..2a14f6afbdd5e 100644 --- a/runtime/dart_isolate.cc +++ b/runtime/dart_isolate.cc @@ -320,6 +320,10 @@ bool DartIsolate::Initialize(Dart_Isolate dart_isolate) { return false; } + if (tonic::LogIfError(Dart_SetDeferredLoadHandler(OnDartLoadLibrary))) { + return false; + } + if (!UpdateThreadPoolNames()) { return false; } @@ -332,6 +336,37 @@ fml::RefPtr DartIsolate::GetMessageHandlingTaskRunner() const { return message_handling_task_runner_; } +bool DartIsolate::LoadLoadingUnit( + intptr_t loading_unit_id, + std::unique_ptr snapshot_data, + std::unique_ptr snapshot_instructions) { + tonic::DartState::Scope scope(this); + + fml::RefPtr dart_snapshot = + DartSnapshot::IsolateSnapshotFromMappings( + std::move(snapshot_data), std::move(snapshot_instructions)); + + Dart_Handle result = Dart_DeferredLoadComplete( + loading_unit_id, dart_snapshot->GetDataMapping(), + dart_snapshot->GetInstructionsMapping()); + if (tonic::LogIfError(result)) { + LoadLoadingUnitFailure(loading_unit_id, Dart_GetError(result), + /*transient*/ true); + return false; + } + loading_unit_snapshots_.insert(dart_snapshot); + return true; +} + +void DartIsolate::LoadLoadingUnitFailure(intptr_t loading_unit_id, + const std::string error_message, + bool transient) { + tonic::DartState::Scope scope(this); + Dart_Handle result = Dart_DeferredLoadCompleteError( + loading_unit_id, error_message.c_str(), transient); + tonic::LogIfError(result); +} + void DartIsolate::SetMessageHandlingTaskRunner( fml::RefPtr runner) { if (!IsRootIsolate() || !runner) { @@ -1003,6 +1038,20 @@ void DartIsolate::OnShutdownCallback() { } } +Dart_Handle DartIsolate::OnDartLoadLibrary(intptr_t loading_unit_id) { + if (Current()->platform_configuration()) { + Current()->platform_configuration()->client()->RequestDartDeferredLibrary( + loading_unit_id); + return Dart_Null(); + } + const std::string error_message = + "Platform Configuration was null. Deferred library load request" + "for loading unit id " + + std::to_string(loading_unit_id) + " was not sent."; + FML_LOG(ERROR) << error_message; + return Dart_NewApiError(error_message.c_str()); +} + DartIsolate::AutoFireClosure::AutoFireClosure(const fml::closure& closure) : closure_(closure) {} diff --git a/runtime/dart_isolate.h b/runtime/dart_isolate.h index 5e6913d52b185..a45d138fa4690 100644 --- a/runtime/dart_isolate.h +++ b/runtime/dart_isolate.h @@ -9,6 +9,7 @@ #include #include #include +#include #include "flutter/common/task_runners.h" #include "flutter/fml/compiler_specific.h" @@ -384,6 +385,15 @@ class DartIsolate : public UIDartState { /// fml::RefPtr GetMessageHandlingTaskRunner() const; + bool LoadLoadingUnit( + intptr_t loading_unit_id, + std::unique_ptr snapshot_data, + std::unique_ptr snapshot_instructions); + + void LoadLoadingUnitFailure(intptr_t loading_unit_id, + const std::string error_message, + bool transient); + private: friend class IsolateConfiguration; class AutoFireClosure { @@ -401,6 +411,7 @@ class DartIsolate : public UIDartState { Phase phase_ = Phase::Unknown; std::vector> kernel_buffers_; std::vector> shutdown_callbacks_; + std::unordered_set> loading_unit_snapshots_; fml::RefPtr message_handling_task_runner_; const bool may_insecurely_connect_to_all_domains_; std::string domain_network_policy_; @@ -492,6 +503,9 @@ class DartIsolate : public UIDartState { static void DartIsolateGroupCleanupCallback( std::shared_ptr* isolate_group_data); + // |Dart_DeferredLoadHandler| + static Dart_Handle OnDartLoadLibrary(intptr_t loading_unit_id); + FML_DISALLOW_COPY_AND_ASSIGN(DartIsolate); }; diff --git a/runtime/dart_isolate_unittests.cc b/runtime/dart_isolate_unittests.cc index a90599fd8a3fb..9856d22f03b1b 100644 --- a/runtime/dart_isolate_unittests.cc +++ b/runtime/dart_isolate_unittests.cc @@ -430,5 +430,102 @@ TEST_F(DartIsolateTest, ASSERT_EQ(create_callback_count, 1u); } +TEST_F(DartIsolateTest, InvalidLoadingUnitFails) { + if (!DartVM::IsRunningPrecompiledCode()) { + FML_LOG(INFO) << "Split AOT does not work in JIT mode"; + return; + } + ASSERT_FALSE(DartVMRef::IsInstanceRunning()); + auto settings = CreateSettingsForFixture(); + auto vm_ref = DartVMRef::Create(settings); + ASSERT_TRUE(vm_ref); + auto vm_data = vm_ref.GetVMData(); + ASSERT_TRUE(vm_data); + TaskRunners task_runners(GetCurrentTestName(), // + GetCurrentTaskRunner(), // + GetCurrentTaskRunner(), // + GetCurrentTaskRunner(), // + GetCurrentTaskRunner() // + ); + auto isolate_configuration = + IsolateConfiguration::InferFromSettings(settings); + auto weak_isolate = DartIsolate::CreateRunningRootIsolate( + vm_data->GetSettings(), // settings + vm_data->GetIsolateSnapshot(), // isolate snapshot + std::move(task_runners), // task runners + nullptr, // window + {}, // snapshot delegate + {}, // hint freed delegate + {}, // io manager + {}, // unref queue + {}, // image decoder + "main.dart", // advisory uri + "main", // advisory entrypoint + DartIsolate::Flags{}, // flags + settings.isolate_create_callback, // isolate create callback + settings.isolate_shutdown_callback, // isolate shutdown callback + "main", // dart entrypoint + std::nullopt, // dart entrypoint library + std::move(isolate_configuration) // isolate configuration + ); + auto root_isolate = weak_isolate.lock(); + ASSERT_TRUE(root_isolate); + ASSERT_EQ(root_isolate->GetPhase(), DartIsolate::Phase::Running); + + auto isolate_data = std::make_unique( + split_aot_symbols_.vm_isolate_data, 0); + auto isolate_instructions = std::make_unique( + split_aot_symbols_.vm_isolate_instrs, 0); + + // Invalid loading unit should fail gracefully with error message. + ASSERT_FALSE(root_isolate->LoadLoadingUnit(3, std::move(isolate_data), + std::move(isolate_instructions))); + ASSERT_TRUE(root_isolate->Shutdown()); +} + +TEST_F(DartIsolateTest, ValidLoadingUnitSucceeds) { + if (!DartVM::IsRunningPrecompiledCode()) { + FML_LOG(INFO) << "Split AOT does not work in JIT mode"; + return; + } + + ASSERT_FALSE(DartVMRef::IsInstanceRunning()); + AddNativeCallback("NotifyNative", + CREATE_NATIVE_ENTRY(([this](Dart_NativeArguments args) { + FML_LOG(ERROR) << "Hello from Dart!"; + Signal(); + }))); + AddNativeCallback( + "NotifySuccess", CREATE_NATIVE_ENTRY([this](Dart_NativeArguments args) { + auto bool_handle = Dart_GetNativeArgument(args, 0); + ASSERT_FALSE(tonic::LogIfError(bool_handle)); + ASSERT_TRUE(tonic::DartConverter::FromDart(bool_handle)); + Signal(); + })); + const auto settings = CreateSettingsForFixture(); + auto vm_ref = DartVMRef::Create(settings); + auto thread = CreateNewThread(); + TaskRunners task_runners(GetCurrentTestName(), // + thread, // + thread, // + thread, // + thread // + ); + auto isolate = + RunDartCodeInIsolate(vm_ref, settings, task_runners, + "canCallDeferredLibrary", {}, GetFixturesPath()); + ASSERT_TRUE(isolate); + ASSERT_EQ(isolate->get()->GetPhase(), DartIsolate::Phase::Running); + Wait(); + + auto isolate_data = std::make_unique( + split_aot_symbols_.vm_isolate_data, 0); + auto isolate_instructions = std::make_unique( + split_aot_symbols_.vm_isolate_instrs, 0); + + ASSERT_TRUE(isolate->get()->LoadLoadingUnit(2, std::move(isolate_data), + std::move(isolate_instructions))); +} + } // namespace testing } // namespace flutter diff --git a/runtime/dart_snapshot.cc b/runtime/dart_snapshot.cc index e9ed35418880a..aac3f28789fa6 100644 --- a/runtime/dart_snapshot.cc +++ b/runtime/dart_snapshot.cc @@ -181,6 +181,17 @@ fml::RefPtr DartSnapshot::IsolateSnapshotFromSettings( return nullptr; } +fml::RefPtr DartSnapshot::IsolateSnapshotFromMappings( + std::shared_ptr snapshot_data, + std::shared_ptr snapshot_instructions) { + auto snapshot = + fml::MakeRefCounted(snapshot_data, snapshot_instructions); + if (snapshot->IsValid()) { + return snapshot; + } + return nullptr; +} + DartSnapshot::DartSnapshot(std::shared_ptr data, std::shared_ptr instructions) : data_(std::move(data)), instructions_(std::move(instructions)) {} diff --git a/runtime/dart_snapshot.h b/runtime/dart_snapshot.h index 15ca157c7a273..e43b89027db1b 100644 --- a/runtime/dart_snapshot.h +++ b/runtime/dart_snapshot.h @@ -102,6 +102,18 @@ class DartSnapshot : public fml::RefCountedThreadSafe { static fml::RefPtr IsolateSnapshotFromSettings( const Settings& settings); + //---------------------------------------------------------------------------- + /// @brief Create an isolate snapshot from existing fml::Mappings. + /// + /// @param[in] snapshot_data The mapping for the heap snapshot. + /// @param[in] snapshot_instructions The mapping for the instructions + /// snapshot. + /// + /// @return A valid isolate snapshot or nullptr. + static fml::RefPtr IsolateSnapshotFromMappings( + std::shared_ptr snapshot_data, + std::shared_ptr snapshot_instructions); + //---------------------------------------------------------------------------- /// @brief Determines if this snapshot contains a heap component. Since /// the instructions component is optional, the method does not diff --git a/runtime/fixtures/runtime_test.dart b/runtime/fixtures/runtime_test.dart index 616e026a5ccce..d559e6ec3d02b 100644 --- a/runtime/fixtures/runtime_test.dart +++ b/runtime/fixtures/runtime_test.dart @@ -2,9 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; import 'dart:isolate'; import 'dart:ui'; +import 'split_lib_test.dart' deferred as splitlib; + void main() { } @@ -25,6 +28,23 @@ void canRegisterNativeCallback() async { print('Called native method from canRegisterNativeCallback'); } +Future? splitLoadFuture = null; + +@pragma('vm:entry-point') +void canCallDeferredLibrary() { + print('In function canCallDeferredLibrary'); + splitLoadFuture = splitlib.loadLibrary() + .then((_) { + print('Deferred load complete'); + notifySuccess(splitlib.splitAdd(10, 23) == 33); + }) + .catchError((_) { + print('Deferred load error'); + notifySuccess(false); + }); + notifyNative(); +} + void notifyNative() native 'NotifyNative'; @pragma('vm:entry-point') diff --git a/runtime/fixtures/split_lib_test.dart b/runtime/fixtures/split_lib_test.dart new file mode 100644 index 0000000000000..5d811fc14c374 --- /dev/null +++ b/runtime/fixtures/split_lib_test.dart @@ -0,0 +1,9 @@ +// 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. + +library splitlib; + +int splitAdd(int i, int j) { + return i + j; +} diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc index 3551e1967134e..04216f5606875 100644 --- a/runtime/runtime_controller.cc +++ b/runtime/runtime_controller.cc @@ -415,6 +415,19 @@ std::optional RuntimeController::GetRootIsolateReturnCode() { return root_isolate_return_code_; } +void RuntimeController::LoadDartDeferredLibrary( + intptr_t loading_unit_id, + std::unique_ptr snapshot_data, + std::unique_ptr snapshot_instructions) { + root_isolate_.lock()->LoadLoadingUnit(loading_unit_id, + std::move(snapshot_data), + std::move(snapshot_instructions)); +} + +void RuntimeController::RequestDartDeferredLibrary(intptr_t loading_unit_id) { + return client_.RequestDartDeferredLibrary(loading_unit_id); +} + RuntimeController::Locale::Locale(std::string language_code_, std::string country_code_, std::string script_code_, diff --git a/runtime/runtime_controller.h b/runtime/runtime_controller.h index 22941c7388a1b..3b4ef073fc411 100644 --- a/runtime/runtime_controller.h +++ b/runtime/runtime_controller.h @@ -11,6 +11,7 @@ #include "flutter/common/task_runners.h" #include "flutter/flow/layers/layer_tree.h" #include "flutter/fml/macros.h" +#include "flutter/fml/mapping.h" #include "flutter/lib/ui/io_manager.h" #include "flutter/lib/ui/text/font_collection.h" #include "flutter/lib/ui/ui_dart_state.h" @@ -473,6 +474,45 @@ class RuntimeController : public PlatformConfigurationClient { /// std::optional GetRootIsolateReturnCode(); + //-------------------------------------------------------------------------- + /// @brief Loads the Dart shared library into the Dart VM. When the + /// Dart library is loaded successfully, the Dart future + /// returned by the originating loadLibrary() call completes. + /// + /// The Dart compiler may generate separate shared libraries + /// files called 'loading units' when libraries are imported + /// as deferred. Each of these shared libraries are identified + /// by a unique loading unit id. Callers should open and resolve + /// a SymbolMapping from the shared library. The Mappings should + /// be moved into this method, as ownership will be assumed by the + /// dart root isolate after successful loading and released after + /// shutdown of the root isolate. The loading unit may not be + /// used after isolate shutdown. If loading fails, the mappings + /// will be released. + /// + /// This method is paired with a RequestDartDeferredLibrary + /// invocation that provides the embedder with the loading unit id + /// of the deferred library to load. + /// + /// + /// @param[in] loading_unit_id The unique id of the deferred library's + /// loading unit, as passed in by + /// RequestDartDeferredLibrary. + /// + /// @param[in] snapshot_data Dart snapshot data of the loading unit's + /// shared library. + /// + /// @param[in] snapshot_data Dart snapshot instructions of the loading + /// unit's shared library. + /// + void LoadDartDeferredLibrary( + intptr_t loading_unit_id, + std::unique_ptr snapshot_data, + std::unique_ptr snapshot_instructions); + + // |PlatformConfigurationClient| + void RequestDartDeferredLibrary(intptr_t loading_unit_id) override; + protected: /// Constructor for Mocks. RuntimeController(RuntimeDelegate& client, TaskRunners p_task_runners); diff --git a/runtime/runtime_delegate.h b/runtime/runtime_delegate.h index 55978b4dbc39f..d2e5e9ff14caf 100644 --- a/runtime/runtime_delegate.h +++ b/runtime/runtime_delegate.h @@ -43,6 +43,8 @@ class RuntimeDelegate { ComputePlatformResolvedLocale( const std::vector& supported_locale_data) = 0; + virtual void RequestDartDeferredLibrary(intptr_t loading_unit_id) = 0; + protected: virtual ~RuntimeDelegate(); }; diff --git a/shell/common/engine.cc b/shell/common/engine.cc index 1b9c02413138b..7ca7ae9e28720 100644 --- a/shell/common/engine.cc +++ b/shell/common/engine.cc @@ -507,20 +507,19 @@ const std::string& Engine::GetLastEntrypointLibrary() const { return last_entry_point_library_; } -// The Following commented out code connects into part 2 of the split AOT -// feature. Left commented out until it lands: - -// // |RuntimeDelegate| -// void Engine::RequestDartDeferredLibrary(intptr_t loading_unit_id) { -// return delegate_.RequestDartDeferredLibrary(loading_unit_id); -// } +// |RuntimeDelegate| +void Engine::RequestDartDeferredLibrary(intptr_t loading_unit_id) { + return delegate_.RequestDartDeferredLibrary(loading_unit_id); +} -void Engine::LoadDartDeferredLibrary(intptr_t loading_unit_id, - const uint8_t* snapshot_data, - const uint8_t* snapshot_instructions) { +void Engine::LoadDartDeferredLibrary( + intptr_t loading_unit_id, + std::unique_ptr snapshot_data, + std::unique_ptr snapshot_instructions) { if (runtime_controller_->IsRootIsolateRunning()) { - // runtime_controller_->LoadDartDeferredLibrary(loading_unit_id, - // snapshot_data, snapshot_instructions); + runtime_controller_->LoadDartDeferredLibrary( + loading_unit_id, std::move(snapshot_data), + std::move(snapshot_instructions)); } } diff --git a/shell/common/engine.h b/shell/common/engine.h index 93a5f2367dbc3..03e676b15b03b 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -11,6 +11,7 @@ #include "flutter/assets/asset_manager.h" #include "flutter/common/task_runners.h" #include "flutter/fml/macros.h" +#include "flutter/fml/mapping.h" #include "flutter/fml/memory/weak_ptr.h" #include "flutter/lib/ui/hint_freed_delegate.h" #include "flutter/lib/ui/painting/image_decoder.h" @@ -268,6 +269,12 @@ class Engine final : public RuntimeDelegate, /// downloaded and loaded into the Dart VM via /// `LoadDartDeferredLibrary` /// + /// Upon encountering errors or otherwise failing to load a + /// loading unit with the specified id, the failure should be + /// directly reported to dart by calling + /// `LoadDartDeferredLibraryFailure` to ensure the waiting dart + /// future completes with an error. + /// /// @param[in] loading_unit_id The unique id of the deferred library's /// loading unit. This id is to be passed /// back into LoadDartDeferredLibrary @@ -790,10 +797,13 @@ class Engine final : public RuntimeDelegate, /// The Dart compiler may generate separate shared libraries /// files called 'loading units' when libraries are imported /// as deferred. Each of these shared libraries are identified - /// by a unique loading unit id. Callers should dlopen the - /// shared library file and use dlsym to resolve the dart - /// symbols. These symbols can then be passed to this method to - /// be dynamically loaded into the VM. + /// by a unique loading unit id. Callers should open and resolve + /// a SymbolMapping from the shared library. The Mappings should + /// be moved into this method, as ownership will be assumed by the + /// dart root isolate after successful loading and released after + /// shutdown of the root isolate. The loading unit may not be + /// used after isolate shutdown. If loading fails, the mappings + /// will be released. /// /// This method is paired with a RequestDartDeferredLibrary /// invocation that provides the embedder with the loading unit id @@ -810,9 +820,10 @@ class Engine final : public RuntimeDelegate, /// @param[in] snapshot_data Dart snapshot instructions of the loading /// unit's shared library. /// - void LoadDartDeferredLibrary(intptr_t loading_unit_id, - const uint8_t* snapshot_data, - const uint8_t* snapshot_instructions); + void LoadDartDeferredLibrary( + intptr_t loading_unit_id, + std::unique_ptr snapshot_data, + std::unique_ptr snapshot_instructions); private: Engine::Delegate& delegate_; @@ -862,11 +873,8 @@ class Engine final : public RuntimeDelegate, std::unique_ptr> ComputePlatformResolvedLocale( const std::vector& supported_locale_data) override; - // The Following commented out code connects into part 2 of the split AOT - // feature. Left commented out until it lands: - - // // |RuntimeDelegate| - // void RequestDartDeferredLibrary(intptr_t loading_unit_id) override; + // |RuntimeDelegate| + void RequestDartDeferredLibrary(intptr_t loading_unit_id) override; void SetNeedsReportTimings(bool value) override; diff --git a/shell/common/platform_view.cc b/shell/common/platform_view.cc index 55d3ebbcc7147..9981202f02c9b 100644 --- a/shell/common/platform_view.cc +++ b/shell/common/platform_view.cc @@ -163,8 +163,8 @@ void PlatformView::RequestDartDeferredLibrary(intptr_t loading_unit_id) {} void PlatformView::LoadDartDeferredLibrary( intptr_t loading_unit_id, - const uint8_t* snapshot_data, - const uint8_t* snapshot_instructions) {} + std::unique_ptr snapshot_data, + std::unique_ptr snapshot_instructions) {} void PlatformView::UpdateAssetManager( std::shared_ptr asset_manager) {} diff --git a/shell/common/platform_view.h b/shell/common/platform_view.h index dd200b5948fdb..f2e72e36d2f7e 100644 --- a/shell/common/platform_view.h +++ b/shell/common/platform_view.h @@ -12,6 +12,7 @@ #include "flutter/common/task_runners.h" #include "flutter/flow/surface.h" #include "flutter/fml/macros.h" +#include "flutter/fml/mapping.h" #include "flutter/fml/memory/weak_ptr.h" #include "flutter/lib/ui/semantics/custom_accessibility_action.h" #include "flutter/lib/ui/semantics/semantics_node.h" @@ -216,12 +217,20 @@ class PlatformView { /// dart library is loaded successfully, the dart future /// returned by the originating loadLibrary() call completes. /// - /// The Dart compiler may generate separate shared library .so + /// The Dart compiler may generate separate shared libraries /// files called 'loading units' when libraries are imported /// as deferred. Each of these shared libraries are identified - /// by a unique loading unit id and can be dynamically loaded - /// into the VM by dlopen-ing and resolving the data and - /// instructions symbols. + /// by a unique loading unit id. Callers should open and resolve + /// a SymbolMapping from the shared library. The Mappings should + /// be moved into this method, as ownership will be assumed by + /// the dart root isolate after successful loading and released + /// after shutdown of the root isolate. The loading unit may not + /// be used after isolate shutdown. If loading fails, the + /// mappings will be released. + /// + /// This method is paired with a RequestDartDeferredLibrary + /// invocation that provides the embedder with the loading unit + /// id of the deferred library to load. /// /// /// @param[in] loading_unit_id The unique id of the deferred library's @@ -235,8 +244,8 @@ class PlatformView { /// virtual void LoadDartDeferredLibrary( intptr_t loading_unit_id, - const uint8_t* snapshot_data, - const uint8_t* snapshot_instructions) = 0; + std::unique_ptr snapshot_data, + std::unique_ptr snapshot_instructions) = 0; // TODO(garyq): Implement a proper asset_resolver replacement instead of // overwriting the entire asset manager. @@ -609,6 +618,12 @@ class PlatformView { /// downloaded and loaded into the Dart VM via /// `LoadDartDeferredLibrary` /// + /// Upon encountering errors or otherwise failing to load a + /// loading unit with the specified id, the failure should be + /// directly reported to dart by calling + /// `LoadDartDeferredLibraryFailure` to ensure the waiting dart + /// future completes with an error. + /// /// @param[in] loading_unit_id The unique id of the deferred library's /// loading unit. This id is to be passed /// back into LoadDartDeferredLibrary @@ -625,10 +640,12 @@ class PlatformView { /// The Dart compiler may generate separate shared libraries /// files called 'loading units' when libraries are imported /// as deferred. Each of these shared libraries are identified - /// by a unique loading unit id. Callers should dlopen the - /// shared library file and use dlsym to resolve the dart - /// symbols. These symbols can then be passed to this method to - /// be dynamically loaded into the VM. + /// by a unique loading unit id. Callers should open and resolve + /// a SymbolMapping from the shared library. The Mappings should + /// be moved into this method, as ownership will be assumed by the + /// dart isolate after successful loading and released after + /// shutdown of the dart isolate. If loading fails, the mappings + /// will naturally go out of scope. /// /// This method is paired with a RequestDartDeferredLibrary /// invocation that provides the embedder with the loading unit id @@ -645,9 +662,10 @@ class PlatformView { /// @param[in] snapshot_data Dart snapshot instructions of the loading /// unit's shared library. /// - virtual void LoadDartDeferredLibrary(intptr_t loading_unit_id, - const uint8_t* snapshot_data, - const uint8_t* snapshot_instructions); + virtual void LoadDartDeferredLibrary( + intptr_t loading_unit_id, + std::unique_ptr snapshot_data, + std::unique_ptr snapshot_instructions); // TODO(garyq): Implement a proper asset_resolver replacement instead of // overwriting the entire asset manager. diff --git a/shell/common/shell.cc b/shell/common/shell.cc index aaf3c3c2242ae..2a23ee783af26 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -1185,11 +1185,12 @@ std::unique_ptr> Shell::ComputePlatformResolvedLocale( return platform_view_->ComputePlatformResolvedLocales(supported_locale_data); } -void Shell::LoadDartDeferredLibrary(intptr_t loading_unit_id, - const uint8_t* snapshot_data, - const uint8_t* snapshot_instructions) { - engine_->LoadDartDeferredLibrary(loading_unit_id, snapshot_data, - snapshot_instructions); +void Shell::LoadDartDeferredLibrary( + intptr_t loading_unit_id, + std::unique_ptr snapshot_data, + std::unique_ptr snapshot_instructions) { + engine_->LoadDartDeferredLibrary(loading_unit_id, std::move(snapshot_data), + std::move(snapshot_instructions)); } void Shell::UpdateAssetManager(std::shared_ptr asset_manager) { diff --git a/shell/common/shell.h b/shell/common/shell.h index 7ac297e5d07c6..c3a743778c8db 100644 --- a/shell/common/shell.h +++ b/shell/common/shell.h @@ -508,9 +508,10 @@ class Shell final : public PlatformView::Delegate, void OnPlatformViewSetNextFrameCallback(const fml::closure& closure) override; // |PlatformView::Delegate| - void LoadDartDeferredLibrary(intptr_t loading_unit_id, - const uint8_t* snapshot_data, - const uint8_t* snapshot_instructions) override; + void LoadDartDeferredLibrary( + intptr_t loading_unit_id, + std::unique_ptr snapshot_data, + std::unique_ptr snapshot_instructions) override; // |PlatformView::Delegate| void UpdateAssetManager(std::shared_ptr asset_manager) override; diff --git a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java index 0ae2fd4961c0e..406392cd28b53 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java @@ -195,13 +195,14 @@ private String loadingUnitIdToModuleName(int loadingUnitId) { public void downloadDynamicFeature(int loadingUnitId, String moduleName) { String resolvedModuleName = - moduleName == null ? moduleName : loadingUnitIdToModuleName(loadingUnitId); + moduleName != null ? moduleName : loadingUnitIdToModuleName(loadingUnitId); if (resolvedModuleName == null) { Log.d(TAG, "Dynamic feature module name was null."); return; } - SplitInstallRequest request = SplitInstallRequest.newBuilder().addModule(moduleName).build(); + SplitInstallRequest request = + SplitInstallRequest.newBuilder().addModule(resolvedModuleName).build(); splitInstallManager // Submits the request to install the module through the @@ -212,7 +213,7 @@ public void downloadDynamicFeature(int loadingUnitId, String moduleName) { // install which is handled in FeatureInstallStateUpdatedListener. .addOnSuccessListener( sessionId -> { - this.sessionIdToName.put(sessionId, moduleName); + this.sessionIdToName.put(sessionId, resolvedModuleName); this.sessionIdToLoadingUnitId.put(sessionId, loadingUnitId); }) .addOnFailureListener( diff --git a/shell/platform/android/platform_view_android.cc b/shell/platform/android/platform_view_android.cc index f5049b23223d6..2d13e567b8038 100644 --- a/shell/platform/android/platform_view_android.cc +++ b/shell/platform/android/platform_view_android.cc @@ -347,10 +347,10 @@ void PlatformViewAndroid::RequestDartDeferredLibrary(intptr_t loading_unit_id) { // |PlatformView| void PlatformViewAndroid::LoadDartDeferredLibrary( intptr_t loading_unit_id, - const uint8_t* snapshot_data, - const uint8_t* snapshot_instructions) { - delegate_.LoadDartDeferredLibrary(loading_unit_id, snapshot_data, - snapshot_instructions); + std::unique_ptr snapshot_data, + std::unique_ptr snapshot_instructions) { + delegate_.LoadDartDeferredLibrary(loading_unit_id, std::move(snapshot_data), + std::move(snapshot_instructions)); } // |PlatformView| diff --git a/shell/platform/android/platform_view_android.h b/shell/platform/android/platform_view_android.h index ff2ac1353f2b3..87ff5bcd917dc 100644 --- a/shell/platform/android/platform_view_android.h +++ b/shell/platform/android/platform_view_android.h @@ -94,9 +94,10 @@ class PlatformViewAndroid final : public PlatformView { const fml::jni::JavaObjectWeakGlobalRef& surface_texture); // |PlatformView| - void LoadDartDeferredLibrary(intptr_t loading_unit_id, - const uint8_t* snapshot_data, - const uint8_t* snapshot_instructions) override; + void LoadDartDeferredLibrary( + intptr_t loading_unit_id, + std::unique_ptr snapshot_data, + std::unique_ptr snapshot_instructions) override; // |PlatformView| void UpdateAssetManager(std::shared_ptr asset_manager) override; diff --git a/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc index a252d167b07c3..ac3c50e83d75c 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc @@ -15,6 +15,8 @@ #include "flutter/assets/directory_asset_bundle.h" #include "flutter/common/settings.h" #include "flutter/fml/file.h" +#include "flutter/fml/mapping.h" +#include "flutter/fml/native_library.h" #include "flutter/fml/platform/android/jni_util.h" #include "flutter/fml/platform/android/jni_weak_ref.h" #include "flutter/fml/platform/android/scoped_java_ref.h" @@ -539,12 +541,8 @@ static void LoadDartDeferredLibrary(JNIEnv* env, std::vector search_paths = fml::jni::StringArrayToVector(env, jSearchPaths); - // TODO: Switch to using the NativeLibrary class, eg: - // - // fml::RefPtr native_lib = - // fml::NativeLibrary::Create(lib_name.c_str()); - // - // Find and open the shared library. + // Use dlopen here to directly check if handle is nullptr before creating a + // NativeLibrary. void* handle = nullptr; while (handle == nullptr && !search_paths.empty()) { std::string path = search_paths.back(); @@ -556,42 +554,20 @@ static void LoadDartDeferredLibrary(JNIEnv* env, "No lib .so found for provided search paths.", true); return; } + fml::RefPtr native_lib = + fml::NativeLibrary::CreateWithHandle(handle, false); // Resolve symbols. - uint8_t* isolate_data = - static_cast(::dlsym(handle, DartSnapshot::kIsolateDataSymbol)); - if (isolate_data == nullptr) { - // Mac sometimes requires an underscore prefix. - std::stringstream underscore_symbol_name; - underscore_symbol_name << "_" << DartSnapshot::kIsolateDataSymbol; - isolate_data = static_cast( - ::dlsym(handle, underscore_symbol_name.str().c_str())); - if (isolate_data == nullptr) { - LoadLoadingUnitFailure(loading_unit_id, - "Could not resolve data symbol in library", true); - return; - } - } - uint8_t* isolate_instructions = static_cast( - ::dlsym(handle, DartSnapshot::kIsolateInstructionsSymbol)); - if (isolate_instructions == nullptr) { - // Mac sometimes requires an underscore prefix. - std::stringstream underscore_symbol_name; - underscore_symbol_name << "_" << DartSnapshot::kIsolateInstructionsSymbol; - isolate_instructions = static_cast( - ::dlsym(handle, underscore_symbol_name.str().c_str())); - if (isolate_data == nullptr) { - LoadLoadingUnitFailure(loading_unit_id, - "Could not resolve instructions symbol in library", - true); - return; - } - } + std::unique_ptr data_mapping = + std::make_unique( + native_lib, DartSnapshot::kIsolateDataSymbol); + std::unique_ptr instructions_mapping = + std::make_unique( + native_lib, DartSnapshot::kIsolateInstructionsSymbol); ANDROID_SHELL_HOLDER->GetPlatformView()->LoadDartDeferredLibrary( - loading_unit_id, isolate_data, isolate_instructions); - - // TODO(garyq): fallback on soPath. + loading_unit_id, std::move(data_mapping), + std::move(instructions_mapping)); } // TODO(garyq): persist additional asset resolvers by updating instead of diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm index fbccf6677b02f..ed0e5544900ee 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm @@ -35,8 +35,9 @@ void OnPlatformViewUnregisterTexture(int64_t texture_id) override {} void OnPlatformViewMarkTextureFrameAvailable(int64_t texture_id) override {} void LoadDartDeferredLibrary(intptr_t loading_unit_id, - const uint8_t* snapshot_data, - const uint8_t* snapshot_instructions) override {} + std::unique_ptr snapshot_data, + std::unique_ptr snapshot_instructions) override { + } void UpdateAssetManager(std::shared_ptr asset_manager) override {} }; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm index d9abef5a093ef..5b11fd3eae409 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm @@ -105,8 +105,9 @@ void OnPlatformViewUnregisterTexture(int64_t texture_id) override {} void OnPlatformViewMarkTextureFrameAvailable(int64_t texture_id) override {} void LoadDartDeferredLibrary(intptr_t loading_unit_id, - const uint8_t* snapshot_data, - const uint8_t* snapshot_instructions) override {} + std::unique_ptr snapshot_data, + std::unique_ptr snapshot_instructions) override { + } void UpdateAssetManager(std::shared_ptr asset_manager) override {} }; diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm index b027b20e915b4..677f4eac350bf 100644 --- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm +++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm @@ -88,8 +88,9 @@ void OnPlatformViewUnregisterTexture(int64_t texture_id) override {} void OnPlatformViewMarkTextureFrameAvailable(int64_t texture_id) override {} void LoadDartDeferredLibrary(intptr_t loading_unit_id, - const uint8_t* snapshot_data, - const uint8_t* snapshot_instructions) override {} + std::unique_ptr snapshot_data, + std::unique_ptr snapshot_instructions) override { + } void UpdateAssetManager(std::shared_ptr asset_manager) override {} }; diff --git a/shell/platform/fuchsia/flutter/platform_view_unittest.cc b/shell/platform/fuchsia/flutter/platform_view_unittest.cc index a54504a92ea74..9e05013045a50 100644 --- a/shell/platform/fuchsia/flutter/platform_view_unittest.cc +++ b/shell/platform/fuchsia/flutter/platform_view_unittest.cc @@ -105,9 +105,10 @@ class MockPlatformViewDelegate : public flutter::PlatformView::Delegate { return nullptr; } // |flutter::PlatformView::Delegate| - void LoadDartDeferredLibrary(intptr_t loading_unit_id, - const uint8_t* snapshot_data, - const uint8_t* snapshot_instructions) {} + void LoadDartDeferredLibrary( + intptr_t loading_unit_id, + std::unique_ptr snapshot_data, + std::unique_ptr snapshot_instructions) {} // |flutter::PlatformView::Delegate| void UpdateAssetManager( std::shared_ptr asset_manager) {} diff --git a/testing/elf_loader.cc b/testing/elf_loader.cc index 618859d00b2f1..6d075ecfc15f7 100644 --- a/testing/elf_loader.cc +++ b/testing/elf_loader.cc @@ -60,6 +60,54 @@ ELFAOTSymbols LoadELFSymbolFromFixturesIfNeccessary() { return symbols; } +ELFAOTSymbols LoadELFSplitSymbolFromFixturesIfNeccessary() { + if (!DartVM::IsRunningPrecompiledCode()) { + return {}; + } + + const auto elf_path = + fml::paths::JoinPaths({GetFixturesPath(), kAOTAppELFSplitFileName}); + + if (!fml::IsFile(elf_path)) { + FML_LOG(ERROR) << "App AOT file does not exist for this fixture. Attempts " + "to launch the Dart VM with these AOT symbols will fail."; + return {}; + } + + ELFAOTSymbols symbols; + + // Must not be freed. + const char* error = nullptr; + +#if OS_FUCHSIA + // TODO(gw280): https://github.com/flutter/flutter/issues/50285 + // Dart doesn't implement Dart_LoadELF on Fuchsia + auto loaded_elf = nullptr; +#else + auto loaded_elf = + Dart_LoadELF(elf_path.c_str(), // file path + 0, // file offset + &error, // error (out) + &symbols.vm_snapshot_data, // vm snapshot data (out) + &symbols.vm_snapshot_instrs, // vm snapshot instrs (out) + &symbols.vm_isolate_data, // vm isolate data (out) + &symbols.vm_isolate_instrs // vm isolate instr (out) + ); +#endif + + if (loaded_elf == nullptr) { + FML_LOG(ERROR) + << "Could not fetch AOT symbols from loaded ELF. Attempts " + "to launch the Dart VM with these AOT symbols will fail. Error: " + << error; + return {}; + } + + symbols.loaded_elf.reset(loaded_elf); + + return symbols; +} + bool PrepareSettingsForAOTWithSymbols(Settings& settings, const ELFAOTSymbols& symbols) { if (!DartVM::IsRunningPrecompiledCode()) { diff --git a/testing/elf_loader.h b/testing/elf_loader.h index a8c37c92172d9..d5861f04dee43 100644 --- a/testing/elf_loader.h +++ b/testing/elf_loader.h @@ -16,6 +16,14 @@ namespace testing { inline constexpr const char* kAOTAppELFFileName = "app_elf_snapshot.so"; +// This file name is what gen_snapshot defaults to. It is based off of the +// name of the base file, with the `2` indicating that this split corresponds +// to loading unit id of 2. The base module id is 1 and is omitted as it is not +// considered a split. If dart changes the naming convention, this should be +// changed to match, however, this is considered unlikely to happen. +inline constexpr const char* kAOTAppELFSplitFileName = + "app_elf_snapshot.so-2.part.so"; + struct LoadedELFDeleter { void operator()(Dart_LoadedElf* elf) { ::Dart_UnloadELF(elf); } }; @@ -40,6 +48,17 @@ struct ELFAOTSymbols { /// ELFAOTSymbols LoadELFSymbolFromFixturesIfNeccessary(); +//------------------------------------------------------------------------------ +/// @brief Attempts to resolve split loading unit AOT symbols from the +/// portable ELF loader. If the dart code does not make use of +/// deferred libraries, then there will be no split .so to load. +/// This only returns valid symbols when the VM is configured for +/// AOT. +/// +/// @return The loaded ELF symbols. +/// +ELFAOTSymbols LoadELFSplitSymbolFromFixturesIfNeccessary(); + //------------------------------------------------------------------------------ /// @brief Prepare the settings objects various AOT mappings resolvers with /// the symbols already loaded. This method does nothing in non-AOT diff --git a/testing/fixture_test.cc b/testing/fixture_test.cc index 3a8a94c138805..5e8f369b4ae3c 100644 --- a/testing/fixture_test.cc +++ b/testing/fixture_test.cc @@ -9,6 +9,7 @@ namespace testing { FixtureTest::FixtureTest() : native_resolver_(std::make_shared()), + split_aot_symbols_(LoadELFSplitSymbolFromFixturesIfNeccessary()), assets_dir_(fml::OpenDirectory(GetFixturesPath(), false, fml::FilePermission::kRead)), diff --git a/testing/fixture_test.h b/testing/fixture_test.h index f48c94319fc99..82a82cb8e2f66 100644 --- a/testing/fixture_test.h +++ b/testing/fixture_test.h @@ -30,6 +30,8 @@ class FixtureTest : public ThreadTest { std::shared_ptr native_resolver_; + ELFAOTSymbols split_aot_symbols_; + private: fml::UniqueFD assets_dir_; ELFAOTSymbols aot_symbols_; diff --git a/testing/testing.gni b/testing/testing.gni index 37a8302702bd8..2e52afff98b82 100644 --- a/testing/testing.gni +++ b/testing/testing.gni @@ -126,6 +126,8 @@ template("dart_snapshot_aot") { # Custom ELF loader is used for Mac and Windows. elf_object = "$target_gen_dir/assets/app_elf_snapshot.so" + loading_unit_manifest = "$target_gen_dir/assets/loading_unit_manifest.json" + outputs = [ elf_object ] args = [ @@ -133,6 +135,7 @@ template("dart_snapshot_aot") { "--lazy_async_stacks", "--deterministic", "--snapshot_kind=app-aot-elf", + "--loading_unit_manifest=" + rebase_path(loading_unit_manifest), "--elf=" + rebase_path(elf_object), rebase_path(invoker.dart_kernel), ]