Skip to content

Commit

Permalink
Eliminates an extra copy when returning messages from the host platfo…
Browse files Browse the repository at this point in the history
…rm to dart.
  • Loading branch information
gaaclarke committed Jun 16, 2022
1 parent 10ff302 commit 394980c
Show file tree
Hide file tree
Showing 13 changed files with 172 additions and 14 deletions.
12 changes: 12 additions & 0 deletions assets/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,15 @@ source_set("assets") {

public_configs = [ "//flutter:config" ]
}

source_set("assets_unittests") {
sources = [ "directory_asset_bundle_unittests.cc" ]

deps = [
":assets",
"//flutter/testing",
]

public_configs = [ "//flutter:config" ]
testonly = true
}
23 changes: 21 additions & 2 deletions assets/directory_asset_bundle.cc
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,27 @@ std::unique_ptr<fml::Mapping> DirectoryAssetBundle::GetAsMapping(
return nullptr;
}

auto mapping = std::make_unique<fml::FileMapping>(fml::OpenFile(
descriptor_, asset_name.c_str(), false, fml::FilePermission::kRead));
// TODO(tbd): Implement this optimization for Fuchsia.
// TODO(tbd): Implement this optimization for Windows.
#if defined(OS_FUCHSIA) || defined(FML_OS_WIN)
auto mapping = std::make_unique<fml::FileMapping>(
fml::OpenFile(descriptor_, asset_name.c_str(), false,
fml::FilePermission::kRead),
std::initializer_list<fml::FileMapping::Protection>(
{fml::FileMapping::Protection::kRead}),
std::initializer_list<fml::FileMapping::Flags>());
#else
// We use a copy-on-write mapping so we can safely pass ownership to Dart
// which currently lacks immutable data buffers.
auto mapping = std::make_unique<fml::FileMapping>(
fml::OpenFile(descriptor_, asset_name.c_str(), false,
fml::FilePermission::kRead),
std::initializer_list<fml::FileMapping::Protection>(
{fml::FileMapping::Protection::kRead,
fml::FileMapping::Protection::kWrite}),
std::initializer_list<fml::FileMapping::Flags>(
{fml::FileMapping::Flags::kCopyOnWrite}));
#endif

if (!mapping->IsValid()) {
return nullptr;
Expand Down
40 changes: 40 additions & 0 deletions assets/directory_asset_bundle_unittests.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// 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/assets/directory_asset_bundle.h"
#include "gtest/gtest.h"

namespace flutter {
namespace testing {

TEST(DirectoryAssetBundle, MappingIsReadWrite) {
fml::ScopedTemporaryDirectory temp_dir;
const char* filename = "foo.txt";
fml::MallocMapping write_mapping(static_cast<uint8_t*>(calloc(1, 4)), 4);
fml::WriteAtomically(temp_dir.fd(), filename, write_mapping);
fml::UniqueFD descriptor =
fml::OpenDirectory(temp_dir.path().c_str(), /*create_if_necessary=*/false,
fml::FilePermission::kRead);
std::unique_ptr<AssetResolver> bundle =
std::make_unique<DirectoryAssetBundle>(
std::move(descriptor), /*is_valid_after_asset_manager_change=*/true);
EXPECT_TRUE(bundle->IsValid());
std::unique_ptr<fml::Mapping> read_mapping = bundle->GetAsMapping(filename);
ASSERT_TRUE(read_mapping);

#if defined(OS_FUCHSIA) || defined(FML_OS_WIN)
ASSERT_FALSE(read_mapping->GetMutableMapping());
#else
ASSERT_TRUE(read_mapping->GetMutableMapping());
EXPECT_EQ(read_mapping->GetSize(), 4u);
read_mapping->GetMutableMapping()[0] = 'A';
EXPECT_EQ(read_mapping->GetMapping()[0], 'A');
std::unique_ptr<fml::Mapping> read_after_write_mapping =
bundle->GetAsMapping(filename);
EXPECT_EQ(read_after_write_mapping->GetMapping()[0], '\0');
#endif
}

} // namespace testing
} // namespace flutter
1 change: 1 addition & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ FILE: ../../../flutter/assets/asset_manager.h
FILE: ../../../flutter/assets/asset_resolver.h
FILE: ../../../flutter/assets/directory_asset_bundle.cc
FILE: ../../../flutter/assets/directory_asset_bundle.h
FILE: ../../../flutter/assets/directory_asset_bundle_unittests.cc
FILE: ../../../flutter/benchmarking/benchmarking.cc
FILE: ../../../flutter/benchmarking/benchmarking.h
FILE: ../../../flutter/benchmarking/library.cc
Expand Down
6 changes: 5 additions & 1 deletion fml/mapping.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace fml {

// FileMapping

uint8_t* FileMapping::GetMutableMapping() {
uint8_t* FileMapping::GetMutableMapping() const {
return mutable_mapping_;
}

Expand Down Expand Up @@ -146,6 +146,10 @@ const uint8_t* MallocMapping::GetMapping() const {
return data_;
}

uint8_t* MallocMapping::GetMutableMapping() const {
return data_;
}

bool MallocMapping::IsDontNeedSafe() const {
return false;
}
Expand Down
31 changes: 27 additions & 4 deletions fml/mapping.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ class Mapping {

virtual const uint8_t* GetMapping() const = 0;

/// Returns a non-const pointer to the data if one is available, otherwise it
/// returns nullptr.
virtual uint8_t* GetMutableMapping() const = 0;

// Whether calling madvise(DONTNEED) on the mapping is non-destructive.
// Generally true for file-mapped memory and false for anonymous memory.
virtual bool IsDontNeedSafe() const = 0;
Expand All @@ -44,9 +48,15 @@ class FileMapping final : public Mapping {
kExecute,
};

explicit FileMapping(const fml::UniqueFD& fd,
std::initializer_list<Protection> protection = {
Protection::kRead});
enum class Flags {
kNone,
kCopyOnWrite,
};

explicit FileMapping(
const fml::UniqueFD& fd,
std::initializer_list<Protection> protection = {Protection::kRead},
std::initializer_list<Flags> flags = {Flags::kNone});

~FileMapping() override;

Expand All @@ -72,7 +82,8 @@ class FileMapping final : public Mapping {
// |Mapping|
bool IsDontNeedSafe() const override;

uint8_t* GetMutableMapping();
// |Mapping|
uint8_t* GetMutableMapping() const override;

bool IsValid() const;

Expand Down Expand Up @@ -103,6 +114,9 @@ class DataMapping final : public Mapping {
// |Mapping|
const uint8_t* GetMapping() const override;

// |Mapping|
uint8_t* GetMutableMapping() const override { return nullptr; }

// |Mapping|
bool IsDontNeedSafe() const override;

Expand All @@ -128,6 +142,9 @@ class NonOwnedMapping final : public Mapping {
// |Mapping|
const uint8_t* GetMapping() const override;

// |Mapping|
uint8_t* GetMutableMapping() const override { return nullptr; }

// |Mapping|
bool IsDontNeedSafe() const override;

Expand Down Expand Up @@ -177,6 +194,9 @@ class MallocMapping final : public Mapping {
// |Mapping|
const uint8_t* GetMapping() const override;

// |Mapping|
uint8_t* GetMutableMapping() const override;

// |Mapping|
bool IsDontNeedSafe() const override;

Expand Down Expand Up @@ -204,6 +224,9 @@ class SymbolMapping final : public Mapping {
// |Mapping|
const uint8_t* GetMapping() const override;

// |Mapping|
uint8_t* GetMutableMapping() const override { return nullptr; }

// |Mapping|
bool IsDontNeedSafe() const override;

Expand Down
16 changes: 14 additions & 2 deletions fml/platform/posix/mapping_posix.cc
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,23 @@ static bool IsWritable(
return false;
}

static bool IsCopyOnWrite(
const std::initializer_list<FileMapping::Flags>& flags) {
for (auto flag : flags) {
if (flag == FileMapping::Flags::kCopyOnWrite) {
return true;
}
}
return false;
}

Mapping::Mapping() = default;

Mapping::~Mapping() = default;

FileMapping::FileMapping(const fml::UniqueFD& handle,
std::initializer_list<Protection> protection)
std::initializer_list<Protection> protection,
std::initializer_list<Flags> flags)
: size_(0), mapping_(nullptr) {
if (!handle.is_valid()) {
return;
Expand All @@ -72,7 +83,8 @@ FileMapping::FileMapping(const fml::UniqueFD& handle,

auto* mapping =
::mmap(nullptr, stat_buffer.st_size, ToPosixProtectionFlags(protection),
is_writable ? MAP_SHARED : MAP_PRIVATE, handle.get(), 0);
is_writable && !IsCopyOnWrite(flags) ? MAP_SHARED : MAP_PRIVATE,
handle.get(), 0);

if (mapping == MAP_FAILED) {
return;
Expand Down
16 changes: 15 additions & 1 deletion fml/platform/win/mapping_win.cc
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,19 @@ static bool IsExecutable(
return false;
}

static bool IsCopyOnWrite(
const std::initializer_list<FileMapping::Flags>& flags) {
for (auto flag : flags) {
if (flag == FileMapping::Flags::kCopyOnWrite) {
return true;
}
}
return false;
}

FileMapping::FileMapping(const fml::UniqueFD& fd,
std::initializer_list<Protection> protections)
std::initializer_list<Protection> protections,
std::initializer_list<Flags> flags)
: size_(0), mapping_(nullptr) {
if (!fd.is_valid()) {
return;
Expand Down Expand Up @@ -82,6 +93,9 @@ FileMapping::FileMapping(const fml::UniqueFD& fd,
return;
}

// TODO(tbd): Implement copy-on-write semantics for Windows. It should
// involve using the `FILE_MAP_COPY` flag with [MapViewOfFile].
FML_DCHECK(!IsCopyOnWrite(flags));
const DWORD desired_access = read_only ? FILE_MAP_READ : FILE_MAP_WRITE;

auto mapping = reinterpret_cast<uint8_t*>(
Expand Down
24 changes: 22 additions & 2 deletions lib/ui/window/platform_message_response_dart.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@

namespace flutter {

namespace {
void MappingFinalizer(void* isolate_callback_data, void* peer) {
delete static_cast<fml::Mapping*>(peer);
}
} // anonymous namespace

PlatformMessageResponseDart::PlatformMessageResponseDart(
tonic::DartPersistentValue callback,
fml::RefPtr<fml::TaskRunner> ui_task_runner)
Expand Down Expand Up @@ -42,8 +48,22 @@ void PlatformMessageResponseDart::Complete(std::unique_ptr<fml::Mapping> data) {
}
tonic::DartState::Scope scope(dart_state);

Dart_Handle byte_buffer =
tonic::DartByteData::Create(data->GetMapping(), data->GetSize());
void* mapping = data->GetMutableMapping();
Dart_Handle byte_buffer;
if (mapping) {
size_t data_size = data->GetSize();
byte_buffer = Dart_NewExternalTypedDataWithFinalizer(
/*type=*/Dart_TypedData_kByteData,
/*data=*/mapping,
/*length=*/data_size,
/*peer=*/data.release(),
/*external_allocation_size=*/data_size,
/*callback=*/MappingFinalizer);

} else {
byte_buffer =
tonic::DartByteData::Create(data->GetMapping(), data->GetSize());
}
tonic::DartInvoke(callback.Release(), {byte_buffer});
}));
}
Expand Down
1 change: 1 addition & 0 deletions runtime/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ if (enable_unittests) {
":libdart",
":runtime",
":runtime_fixtures",
"//flutter/assets:assets_unittests",
"//flutter/common",
"//flutter/fml",
"//flutter/lib/snapshot",
Expand Down
2 changes: 2 additions & 0 deletions shell/platform/android/apk_asset_provider.cc
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ class APKAssetMapping : public fml::Mapping {
return reinterpret_cast<const uint8_t*>(AAsset_getBuffer(asset_));
}

uint8_t* GetMutableMapping() const override { return nullptr; }

bool IsDontNeedSafe() const override { return !AAsset_isAllocated(asset_); }

private:
Expand Down
11 changes: 9 additions & 2 deletions shell/platform/darwin/common/buffer_conversions.mm
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ explicit NSDataMapping(NSData* data) : data_([data retain]) {}
return static_cast<const uint8_t*>([data_.get() bytes]);
}

uint8_t* GetMutableMapping() const override {
id id_data = data_.get();
// In practice these are all NSMutableData (see ConvertMappingToNSData).
FML_DCHECK([id_data respondsToSelector:@selector(mutableBytes)]);
return static_cast<uint8_t*>([id_data mutableBytes]);
}

bool IsDontNeedSafe() const override { return false; }

private:
Expand All @@ -33,15 +40,15 @@ explicit NSDataMapping(NSData* data) : data_([data retain]) {}

NSData* ConvertMappingToNSData(fml::MallocMapping buffer) {
size_t size = buffer.GetSize();
return [NSData dataWithBytesNoCopy:buffer.Release() length:size];
return [NSMutableData dataWithBytesNoCopy:buffer.Release() length:size];
}

std::unique_ptr<fml::Mapping> ConvertNSDataToMappingPtr(NSData* data) {
return std::make_unique<NSDataMapping>(data);
}

NSData* CopyMappingPtrToNSData(std::unique_ptr<fml::Mapping> mapping) {
return [NSData dataWithBytes:mapping->GetMapping() length:mapping->GetSize()];
return [NSMutableData dataWithBytes:mapping->GetMapping() length:mapping->GetSize()];
}

} // namespace flutter
3 changes: 3 additions & 0 deletions shell/platform/fuchsia/flutter/file_in_namespace_buffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ class FileInNamespaceBuffer final : public fml::Mapping {
// |fml::Mapping|
const uint8_t* GetMapping() const override;

// |fml::Mapping|
uint8_t* GetMutableMapping() const override { return nullptr; }

// |fml::Mapping|
size_t GetSize() const override;

Expand Down

0 comments on commit 394980c

Please sign in to comment.