Commit af447700 authored by Michael Lippautz's avatar Michael Lippautz Committed by Commit Bot

gc: Add PageMemory and friends

Adds the following abstractions:
- MemoryRegion: Simple Address and size pair.
- PageMemory: Abstraction holding an overall reservation and some
  writeable area.
- PageMemoryRegion: Either a contiguous area of normal pages or an
  area for a single large page. Creates guard pages around writable
  regions.

Guard regions are always added, independently of whether the provided
allocator can actually commit them properly and capped at a maximum of
16KiB. In practice this will result in all platforms except PPC
getting protected regions and PPC only getting an unprotected region.

Drive-by:
- Remove multi-threaded handling of PageMemoryRegion as all data
  structures are thread local and no regions are shared across threads.

Change-Id: I1232216b13ce51482676b16a467dd97d141ac4c7
Bug: 1056170
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2083304
Commit-Queue: Michael Lippautz <mlippautz@chromium.org>
Reviewed-by: default avatarAnton Bikineev <bikineev@chromium.org>
Reviewed-by: default avatarOmer Katz <omerkatz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#747210}
parent 3f448e61
......@@ -7,12 +7,14 @@ import("//testing/test.gni")
component("gc") {
sources = [
"core/gc.cc",
"core/gc_export.h",
"core/globals.h",
"core/page_memory.cc",
"core/page_memory.h",
"public/platform.h",
]
deps = []
deps = [ "//base" ]
defines = [ "GC_IMPLEMENTATION=1" ]
}
......@@ -38,10 +40,13 @@ source_set("test_support") {
source_set("unit_tests") {
testonly = true
sources = []
sources = [ "core/page_memory_basic_test.cc" ]
if (use_partition_alloc) {
sources += [ "test/base_allocator_test.cc" ]
sources += [
"core/page_memory_test.cc",
"test/base_allocator_test.cc",
]
}
deps = [
......
// Copyright 2020 The Chromium 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 "components/gc/core/gc_export.h"
namespace gc {
namespace internal {
GC_EXPORT void Dummy() {
// TODO(mlippautz): Placeholder to force building a library. Remove as soon as
// actual code is available.
}
} // namespace internal
} // namespace gc
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_GC_CORE_GLOBALS_H_
#define COMPONENTS_GC_CORE_GLOBALS_H_
#include <stddef.h>
#include <stdint.h>
namespace gc {
namespace internal {
using Address = uint8_t*;
// Page size of normal pages used for allocation. Actually usable area on the
// page depends on pager headers and guard pages.
constexpr size_t kPageSizeLog2 = 17;
constexpr size_t kPageSize = 1 << kPageSizeLog2; // 128 KiB.
constexpr size_t kPageOffsetMask = kPageSize - 1;
constexpr size_t kPageBaseMask = ~kPageOffsetMask;
// Guard pages are always put into memory. Whether they are actually protected
// depends on the allocator provided to the garbage collector.
constexpr size_t kGuardPageSize = 4096;
static_assert((kPageSize & (kPageSize - 1)) == 0,
"kPageSize must be power of 2");
static_assert((kGuardPageSize & (kGuardPageSize - 1)) == 0,
"kGuardPageSize must be power of 2");
} // namespace internal
} // namespace gc
#endif // COMPONENTS_GC_CORE_GLOBALS_H_
// Copyright 2020 The Chromium 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 "components/gc/core/page_memory.h"
#include "base/bits.h"
#include "base/memory/ptr_util.h"
#include "components/gc/core/globals.h"
namespace gc {
namespace internal {
namespace {
void Unprotect(PageAllocator* allocator, const PageMemory& page_memory) {
if (SupportsCommittingGuardPages(allocator)) {
CHECK(allocator->SetPermissions(page_memory.writeable_region().base(),
page_memory.writeable_region().size(),
PageAllocator::Permission::kReadWrite));
} else {
// No protection in case the allocator cannot commit at the required
// granularity. Only protect if the allocator supports committing at that
// granularity.
//
// The allocator needs to support committing the overall range.
CHECK_EQ(0u,
page_memory.overall_region().size() % allocator->CommitPageSize());
CHECK(allocator->SetPermissions(page_memory.overall_region().base(),
page_memory.overall_region().size(),
PageAllocator::Permission::kReadWrite));
}
}
MemoryRegion GuardMemoryRegion(const MemoryRegion overall_page_region) {
// Always add guard pages, independently of whether they are actually
// protected or not.
MemoryRegion writeable_page_region(
overall_page_region.base() + kGuardPageSize,
overall_page_region.size() - 2 * kGuardPageSize);
DCHECK(overall_page_region.Contains(writeable_page_region));
return writeable_page_region;
}
MemoryRegion ReserveMemoryRegion(PageAllocator* allocator,
size_t allocation_size) {
void* region_memory =
allocator->AllocatePages(nullptr, allocation_size, kPageSize,
PageAllocator::Permission::kNoAccess);
const MemoryRegion reserved_region(static_cast<Address>(region_memory),
allocation_size);
DCHECK_EQ(reserved_region.base() + allocation_size, reserved_region.end());
return reserved_region;
}
} // namespace
PageMemoryRegion::PageMemoryRegion(PageAllocator* allocator,
MemoryRegion reserved_region,
bool is_large)
: allocator_(allocator),
reserved_region_(reserved_region),
is_large_(is_large) {}
PageMemoryRegion::~PageMemoryRegion() {
allocator_->FreePages(reserved_region().base(), reserved_region().size());
}
NormalPageMemoryRegion::NormalPageMemoryRegion(PageAllocator* allocator)
: PageMemoryRegion(
allocator,
ReserveMemoryRegion(allocator,
base::bits::Align(kPageSize * kNumPageRegions,
allocator->AllocatePageSize())),
false) {
for (size_t i = 0; i < kNumPageRegions; ++i) {
const MemoryRegion overall_page_region(
reserved_region_.base() + i * kPageSize, kPageSize);
DCHECK(reserved_region_.Contains(overall_page_region));
const MemoryRegion writeable_page_region =
GuardMemoryRegion(overall_page_region);
page_memories_[i] = PageMemory(overall_page_region, writeable_page_region);
}
}
NormalPageMemoryRegion::~NormalPageMemoryRegion() = default;
void NormalPageMemoryRegion::UnprotectForTesting() {
for (size_t i = 0; i < kNumPageRegions; ++i) {
Unprotect(allocator_, page_memories_[i]);
}
}
LargePageMemoryRegion::LargePageMemoryRegion(PageAllocator* allocator,
size_t length)
: PageMemoryRegion(
allocator,
ReserveMemoryRegion(allocator,
base::bits::Align(length + 2 * kGuardPageSize,
allocator->AllocatePageSize())),
true) {
const MemoryRegion writeable_page_region =
GuardMemoryRegion(reserved_region_);
page_memory_ = PageMemory(reserved_region_, writeable_page_region);
}
LargePageMemoryRegion::~LargePageMemoryRegion() = default;
void LargePageMemoryRegion::UnprotectForTesting() {
Unprotect(allocator_, page_memory_);
}
} // namespace internal
} // namespace gc
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_GC_CORE_PAGE_MEMORY_H_
#define COMPONENTS_GC_CORE_PAGE_MEMORY_H_
#include <array>
#include <memory>
#include "base/logging.h"
#include "components/gc/core/gc_export.h"
#include "components/gc/core/globals.h"
#include "components/gc/public/platform.h"
namespace gc {
namespace internal {
// Returns true if the provided allocator supports committing at the required
// granularity.
inline bool SupportsCommittingGuardPages(PageAllocator* allocator) {
return kGuardPageSize % allocator->CommitPageSize() == 0;
}
class GC_EXPORT MemoryRegion final {
public:
MemoryRegion() = default;
MemoryRegion(Address base, size_t size) : base_(base), size_(size) {
DCHECK(base);
DCHECK_LT(0u, size);
}
Address base() const { return base_; }
size_t size() const { return size_; }
Address end() const { return base_ + size_; }
bool Contains(Address addr) const {
return (reinterpret_cast<uintptr_t>(addr) -
reinterpret_cast<uintptr_t>(base_)) < size_;
}
bool Contains(const MemoryRegion& other) const {
return base_ <= other.base() && other.end() <= end();
}
private:
Address base_ = nullptr;
size_t size_ = 0;
};
// PageMemory provides the backing of a single normal or large page.
class GC_EXPORT PageMemory final {
public:
PageMemory() = default;
PageMemory(MemoryRegion overall, MemoryRegion writable)
: overall_(overall), writable_(writable) {
DCHECK(overall.Contains(writable));
}
const MemoryRegion overall_region() const { return overall_; }
const MemoryRegion writeable_region() const { return writable_; }
private:
MemoryRegion overall_;
MemoryRegion writable_;
};
class GC_EXPORT PageMemoryRegion {
public:
virtual ~PageMemoryRegion();
MemoryRegion reserved_region() const { return reserved_region_; }
bool is_large() const { return is_large_; }
// Disallow copy.
PageMemoryRegion(const PageMemoryRegion&) = delete;
PageMemoryRegion& operator=(const PageMemoryRegion&) = delete;
virtual void UnprotectForTesting() = 0;
protected:
PageMemoryRegion(PageAllocator*, MemoryRegion, bool);
PageAllocator* const allocator_;
const MemoryRegion reserved_region_;
const bool is_large_;
};
// NormalPageMemoryRegion serves kNumPageRegions normal-sized PageMemory object.
class GC_EXPORT NormalPageMemoryRegion final : public PageMemoryRegion {
public:
static constexpr size_t kNumPageRegions = 10;
explicit NormalPageMemoryRegion(PageAllocator*);
~NormalPageMemoryRegion() override;
const PageMemory* begin() { return page_memories_.cbegin(); }
const PageMemory* end() { return page_memories_.cend(); }
void UnprotectForTesting() final;
private:
std::array<PageMemory, kNumPageRegions> page_memories_ = {};
};
// LargePageMemoryRegion serves a single large PageMemory object.
class GC_EXPORT LargePageMemoryRegion final : public PageMemoryRegion {
public:
LargePageMemoryRegion(PageAllocator*, size_t);
~LargePageMemoryRegion() override;
const PageMemory* page_memory() const { return &page_memory_; }
void UnprotectForTesting() final;
private:
PageMemory page_memory_;
};
} // namespace internal
} // namespace gc
#endif // COMPONENTS_GC_CORE_PAGE_MEMORY_H_
// Copyright 2020 The Chromium 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 "components/gc/core/page_memory.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace gc {
namespace internal {
namespace test {
TEST(MemoryRegionTest, Construct) {
uint8_t dummy;
constexpr size_t kSize = 17;
const MemoryRegion region(&dummy, kSize);
EXPECT_EQ(&dummy, region.base());
EXPECT_EQ(kSize, region.size());
EXPECT_EQ(&dummy + kSize, region.end());
}
TEST(MemoryRegionTest, ContainsAddress) {
uint8_t dummy;
constexpr size_t kSize = 7;
const MemoryRegion region(&dummy, kSize);
EXPECT_FALSE(region.Contains(&dummy - 1));
EXPECT_TRUE(region.Contains(&dummy));
EXPECT_TRUE(region.Contains(&dummy + kSize - 1));
EXPECT_FALSE(region.Contains(&dummy + kSize));
}
TEST(MemoryRegionTest, ContainsMemoryRegion) {
uint8_t dummy;
constexpr size_t kSize = 7;
const MemoryRegion region(&dummy, kSize);
const MemoryRegion contained_region1(&dummy, kSize - 1);
EXPECT_TRUE(region.Contains(contained_region1));
const MemoryRegion contained_region2(&dummy + 1, kSize - 1);
EXPECT_TRUE(region.Contains(contained_region2));
const MemoryRegion not_contained_region1(&dummy - 1, kSize);
EXPECT_FALSE(region.Contains(not_contained_region1));
const MemoryRegion not_contained_region2(&dummy + kSize, 1);
EXPECT_FALSE(region.Contains(not_contained_region2));
}
TEST(PageMemoryTest, Construct) {
uint8_t dummy;
constexpr size_t kOverallSize = 17;
const MemoryRegion overall_region(&dummy, kOverallSize);
const MemoryRegion writeable_region(&dummy + 1, kOverallSize - 2);
const PageMemory page_memory(overall_region, writeable_region);
EXPECT_EQ(&dummy, page_memory.overall_region().base());
EXPECT_EQ(&dummy + kOverallSize, page_memory.overall_region().end());
EXPECT_EQ(&dummy + 1, page_memory.writeable_region().base());
EXPECT_EQ(&dummy + kOverallSize - 1, page_memory.writeable_region().end());
}
#if DCHECK_IS_ON()
TEST(PageMemoryDeathTest, ConstructNonContainedRegions) {
uint8_t dummy;
constexpr size_t kOverallSize = 17;
const MemoryRegion overall_region(&dummy, kOverallSize);
const MemoryRegion writeable_region(&dummy + 1, kOverallSize);
EXPECT_DEATH_IF_SUPPORTED(PageMemory(overall_region, writeable_region), "");
}
#endif // DCHECK_IS_ON()
} // namespace test
} // namespace internal
} // namespace gc
// Copyright 2020 The Chromium 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 "components/gc/core/page_memory.h"
#include "build/build_config.h"
#include "components/gc/test/base_allocator.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace gc {
namespace internal {
namespace test {
TEST(PageMemoryRegionTest, NormalPageMemoryRegion) {
gc::test::BaseAllocator allocator;
auto pmr = std::make_unique<NormalPageMemoryRegion>(&allocator);
constexpr size_t kExpectedPageMemories = 10;
size_t page_memory_cnt = 0;
MemoryRegion prev_overall;
for (auto& pm : *pmr) {
page_memory_cnt++;
// Previous PageMemory aligns with the current one.
if (prev_overall.base()) {
EXPECT_EQ(prev_overall.end(), pm.overall_region().base());
}
prev_overall =
MemoryRegion(pm.overall_region().base(), pm.overall_region().size());
// Writeable region is contained in overall region.
EXPECT_TRUE(pm.overall_region().Contains(pm.writeable_region()));
// Front guard page.
EXPECT_EQ(pm.writeable_region().base(),
pm.overall_region().base() + kGuardPageSize);
// Back guard page.
EXPECT_EQ(pm.overall_region().end(),
pm.writeable_region().end() + kGuardPageSize);
}
EXPECT_EQ(kExpectedPageMemories, page_memory_cnt);
}
TEST(PageMemoryRegionTest, LargePageMemoryRegion) {
gc::test::BaseAllocator allocator;
auto pmr = std::make_unique<LargePageMemoryRegion>(&allocator, 1024);
pmr->UnprotectForTesting();
// Only one PageMemory.
const auto* pm = pmr->page_memory();
EXPECT_LE(1024u, pm->writeable_region().size());
EXPECT_EQ(0u, pm->writeable_region().base()[0]);
EXPECT_EQ(0u, pm->writeable_region().end()[-1]);
}
TEST(PageMemoryRegionTest, PlatformUsesGuardPages) {
// This tests that the testing allocator actually uses protected guard
// regions.
gc::test::BaseAllocator allocator;
#ifdef ARCH_CPU_PPC64
EXPECT_FALSE(SupportsCommittingGuardPages(&allocator));
#else // ARCH_CPU_PPC64
EXPECT_TRUE(SupportsCommittingGuardPages(&allocator));
#endif // ARCH_CPU_PPC64
}
namespace {
void access(volatile uint8_t) {}
} // namespace
TEST(PageMemoryRegionDeathTest, ReservationIsFreed) {
gc::test::BaseAllocator allocator;
Address base;
{
auto pmr = std::make_unique<LargePageMemoryRegion>(&allocator, 1024);
base = pmr->reserved_region().base();
}
EXPECT_DEATH(access(base[0]), "");
}
TEST(PageMemoryRegionDeathTest, FrontGuardPageAccessCrashes) {
gc::test::BaseAllocator allocator;
auto pmr = std::make_unique<NormalPageMemoryRegion>(&allocator);
if (SupportsCommittingGuardPages(&allocator)) {
EXPECT_DEATH(access(pmr->begin()->overall_region().base()[0]), "");
}
}
TEST(PageMemoryRegionDeathTest, BackGuardPageAccessCrashes) {
gc::test::BaseAllocator allocator;
auto pmr = std::make_unique<NormalPageMemoryRegion>(&allocator);
if (SupportsCommittingGuardPages(&allocator)) {
EXPECT_DEATH(access(pmr->begin()->writeable_region().end()[0]), "");
}
}
} // namespace test
} // namespace internal
} // namespace gc
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment