Skip to content

Commit 405ceaa

Browse files
committed
[scudo] Manage pages with MemMap in SizeClassAllocator64
Introduce a new data structure to manage the allocated pages from the system. This is meant to deprecate certain memory system call wrappers in Scudo, e.g., map()/unmap(). Besides, we would like to make MapPlatformData to be appeared in platform specific data structure only. Given that there are several allocators in Scudo and each of them has different way of page management. The deprecation will be done in several CLs. In this commit, we start from SizeClassAllocator64. Reviewed By: cferris Differential Revision: https://reviews.llvm.org/D146009
1 parent 3c76e5f commit 405ceaa

File tree

6 files changed

+338
-15
lines changed

6 files changed

+338
-15
lines changed

compiler-rt/lib/scudo/standalone/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ set(SCUDO_HEADERS
7171
list.h
7272
local_cache.h
7373
memtag.h
74+
mem_map.h
75+
mem_map_base.h
7476
mutex.h
7577
options.h
7678
platform.h
@@ -104,6 +106,7 @@ set(SCUDO_SOURCES
104106
flags.cpp
105107
fuchsia.cpp
106108
linux.cpp
109+
mem_map.cpp
107110
release.cpp
108111
report.cpp
109112
rss_limit_checker.cpp
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
//===-- mem_map.cpp ---------------------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "mem_map.h"
10+
11+
#include "common.h"
12+
13+
namespace scudo {
14+
15+
bool MemMapDefault::mapImpl(uptr Addr, uptr Size, const char *Name,
16+
uptr Flags) {
17+
void *MappedAddr =
18+
::scudo::map(reinterpret_cast<void *>(Addr), Size, Name, Flags, &Data);
19+
if (MappedAddr == nullptr)
20+
return false;
21+
Base = reinterpret_cast<uptr>(MappedAddr);
22+
Capacity = Size;
23+
return true;
24+
}
25+
26+
void MemMapDefault::unmapImpl(uptr Addr, uptr Size) {
27+
if (Size == Capacity) {
28+
Base = Capacity = 0;
29+
} else {
30+
if (Base == Addr)
31+
Base = Addr + Size;
32+
Capacity -= Size;
33+
}
34+
35+
::scudo::unmap(reinterpret_cast<void *>(Addr), Size, UNMAP_ALL, &Data);
36+
}
37+
38+
bool MemMapDefault::remapImpl(uptr Addr, uptr Size, const char *Name,
39+
uptr Flags) {
40+
void *RemappedAddr =
41+
::scudo::map(reinterpret_cast<void *>(Addr), Size, Name, Flags, &Data);
42+
return reinterpret_cast<uptr>(RemappedAddr) == Addr;
43+
}
44+
45+
void MemMapDefault::releaseAndZeroPagesToOSImpl(uptr From, uptr Size) {
46+
return ::scudo::releasePagesToOS(Base, From - Base, Size, &Data);
47+
}
48+
49+
void ReservedMemoryDefault::releaseImpl() {
50+
::scudo::unmap(reinterpret_cast<void *>(Base), Capacity, UNMAP_ALL, &Data);
51+
}
52+
53+
bool ReservedMemoryDefault::createImpl(uptr Addr, uptr Size, const char *Name,
54+
uptr Flags) {
55+
void *Reserved = ::scudo::map(reinterpret_cast<void *>(Addr), Size, Name,
56+
Flags | MAP_NOACCESS, &Data);
57+
if (Reserved == nullptr)
58+
return false;
59+
60+
Base = reinterpret_cast<uptr>(Reserved);
61+
Capacity = Size;
62+
63+
return true;
64+
}
65+
66+
ReservedMemoryDefault::MemMapT ReservedMemoryDefault::dispatchImpl(uptr Addr,
67+
uptr Size) {
68+
ReservedMemoryDefault::MemMapT NewMap(Addr, Size);
69+
NewMap.setMapPlatformData(Data);
70+
return NewMap;
71+
}
72+
73+
} // namespace scudo
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
//===-- mem_map.h -----------------------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef SCUDO_MEM_MAP_H_
10+
#define SCUDO_MEM_MAP_H_
11+
12+
#include "mem_map_base.h"
13+
14+
#include "common.h"
15+
#include "internal_defs.h"
16+
17+
// TODO: This is only used for `MapPlatformData`. Remove these includes when we
18+
// have all three platform specific `MemMap` and `ReservedMemory`
19+
// implementations.
20+
#include "fuchsia.h"
21+
#include "linux.h"
22+
#include "trusty.h"
23+
24+
namespace scudo {
25+
26+
// This will be deprecated when every allocator has been supported by each
27+
// platform's `MemMap` implementation.
28+
class MemMapDefault final : public MemMapBase<MemMapDefault> {
29+
public:
30+
constexpr MemMapDefault() = default;
31+
MemMapDefault(uptr Base, uptr Capacity) : Base(Base), Capacity(Capacity) {}
32+
33+
// Impls for base functions.
34+
bool mapImpl(uptr Addr, uptr Size, const char *Name, uptr Flags);
35+
void unmapImpl(uptr Addr, uptr Size);
36+
bool remapImpl(uptr Addr, uptr Size, const char *Name, uptr Flags);
37+
void releasePagesToOSImpl(uptr From, uptr Size) {
38+
return releaseAndZeroPagesToOSImpl(From, Size);
39+
}
40+
void releaseAndZeroPagesToOSImpl(uptr From, uptr Size);
41+
uptr getBaseImpl() { return Base; }
42+
uptr getCapacityImpl() { return Capacity; }
43+
44+
void setMapPlatformData(MapPlatformData &NewData) { Data = NewData; }
45+
46+
private:
47+
uptr Base = 0;
48+
uptr Capacity = 0;
49+
MapPlatformData Data = {};
50+
};
51+
52+
// This will be deprecated when every allocator has been supported by each
53+
// platform's `MemMap` implementation.
54+
class ReservedMemoryDefault final
55+
: public ReservedMemory<ReservedMemoryDefault, MemMapDefault> {
56+
public:
57+
constexpr ReservedMemoryDefault() = default;
58+
59+
bool createImpl(uptr Addr, uptr Size, const char *Name, uptr Flags);
60+
void releaseImpl();
61+
MemMapT dispatchImpl(uptr Addr, uptr Size);
62+
uptr getBaseImpl() { return Base; }
63+
uptr getCapacityImpl() { return Capacity; }
64+
65+
private:
66+
uptr Base = 0;
67+
uptr Capacity = 0;
68+
MapPlatformData Data = {};
69+
};
70+
71+
#if SCUDO_LINUX
72+
using ReservedMemoryT = ReservedMemoryDefault;
73+
using MemMapT = ReservedMemoryT::MemMapT;
74+
#elif SCUDO_FUCHSIA
75+
using ReservedMemoryT = ReservedMemoryDefault;
76+
using MemMapT = ReservedMemoryT::MemMapT;
77+
#elif SCUDO_TRUSTY
78+
using ReservedMemoryT = ReservedMemoryDefault;
79+
using MemMapT = ReservedMemoryT::MemMapT;
80+
#else
81+
#error \
82+
"Unsupported platform, please implement the ReservedMemory for your platform!"
83+
#endif
84+
85+
} // namespace scudo
86+
87+
#endif // SCUDO_MEM_MAP_H_
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
//===-- mem_map_base.h ------------------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef SCUDO_MEM_MAP_BASE_H_
10+
#define SCUDO_MEM_MAP_BASE_H_
11+
12+
#include "common.h"
13+
14+
namespace scudo {
15+
16+
// In Scudo, every memory operation will be fulfilled through a
17+
// platform-specific `MemMap` instance. The essential APIs are listed in the
18+
// `MemMapBase` below. This is implemented in CRTP, so for each implementation,
19+
// it has to implement all of the 'Impl' named functions.
20+
template <class Derived> class MemMapBase {
21+
public:
22+
constexpr MemMapBase() = default;
23+
24+
// This is used to map a new set of contiguous pages. Note that the `Addr` is
25+
// only a suggestion to the system.
26+
bool map(uptr Addr, uptr Size, const char *Name, uptr Flags = 0) {
27+
DCHECK(!isAllocated());
28+
return invokeImpl(&Derived::mapImpl, Addr, Size, Name, Flags);
29+
}
30+
31+
// This is used to unmap partial/full pages from the beginning or the end.
32+
// I.e., the result pages are expected to be still contiguous.
33+
void unmap(uptr Addr, uptr Size) {
34+
DCHECK(isAllocated());
35+
DCHECK((Addr == getBase()) || (Addr + Size == getBase() + getCapacity()));
36+
invokeImpl(&Derived::unmapImpl, Addr, Size);
37+
}
38+
39+
// This is used to remap a mapped range (either from map() or dispatched from
40+
// ReservedMemory). For example, we have reserved several pages and then we
41+
// want to remap them with different accessibility.
42+
bool remap(uptr Addr, uptr Size, const char *Name, uptr Flags = 0) {
43+
DCHECK(isAllocated());
44+
DCHECK((Addr >= getBase()) || (Addr + Size <= getBase() + getCapacity()));
45+
return invokeImpl(&Derived::remapImpl, Addr, Size, Name, Flags);
46+
}
47+
48+
// Suggest releasing a set of contiguous physical pages back to the OS. Note
49+
// that only physical pages are supposed to be released. Any release of
50+
// virtual pages may lead to undefined behavior.
51+
void releasePagesToOS(uptr From, uptr Size) {
52+
DCHECK(isAllocated());
53+
DCHECK((From >= getBase()) || (From + Size <= getBase() + getCapacity()));
54+
invokeImpl(&Derived::releasePagesToOSImpl, From, Size);
55+
}
56+
// This is similar to the above one except that any subsequent access to the
57+
// released pages will return with zero-filled pages.
58+
void releaseAndZeroPagesToOS(uptr From, uptr Size) {
59+
DCHECK(isAllocated());
60+
DCHECK((From >= getBase()) || (From + Size <= getBase() + getCapacity()));
61+
invokeImpl(&Derived::releaseAndZeroPagesToOSImpl, From, Size);
62+
}
63+
64+
uptr getBase() { return invokeImpl(&Derived::getBaseImpl); }
65+
uptr getCapacity() { return invokeImpl(&Derived::getCapacityImpl); }
66+
67+
bool isAllocated() { return getBase() != 0U; }
68+
69+
protected:
70+
template <typename R, typename... Args>
71+
R invokeImpl(R (Derived::*MemFn)(Args...), Args... args) {
72+
return (static_cast<Derived *>(this)->*MemFn)(args...);
73+
}
74+
};
75+
76+
// `ReservedMemory` is a special memory handle which can be viewed as a page
77+
// allocator. `ReservedMemory` will reserve a contiguous pages and the later
78+
// page request can be fulfilled at the designated address. This is used when
79+
// we want to ensure the virtual address of the MemMap will be in a known range.
80+
// This is implemented in CRTP, so for each
81+
// implementation, it has to implement all of the 'Impl' named functions.
82+
template <class Derived, typename MemMapTy> class ReservedMemory {
83+
public:
84+
using MemMapT = MemMapTy;
85+
constexpr ReservedMemory() = default;
86+
87+
// Reserve a chunk of memory at a suggested address.
88+
bool create(uptr Addr, uptr Size, const char *Name, uptr Flags = 0) {
89+
DCHECK(!isCreated());
90+
return invokeImpl(&Derived::createImpl, Addr, Size, Name, Flags);
91+
}
92+
93+
// Release the entire reserved memory.
94+
void release() {
95+
DCHECK(isCreated());
96+
invokeImpl(&Derived::releaseImpl);
97+
}
98+
99+
// Dispatch a sub-range of reserved memory. Note that any fragmentation of
100+
// the reserved pages is managed by each implementation.
101+
MemMapT dispatch(uptr Addr, uptr Size) {
102+
DCHECK(isCreated());
103+
DCHECK((Addr >= getBase()) || (Addr + Size <= getBase() + getCapacity()));
104+
return invokeImpl(&Derived::dispatchImpl, Addr, Size);
105+
}
106+
107+
uptr getBase() { return invokeImpl(&Derived::getBaseImpl); }
108+
uptr getCapacity() { return invokeImpl(&Derived::getCapacityImpl); }
109+
110+
bool isCreated() { return getBase() != 0U; }
111+
112+
protected:
113+
template <typename R, typename... Args>
114+
R invokeImpl(R (Derived::*MemFn)(Args...), Args... args) {
115+
return (static_cast<Derived *>(this)->*MemFn)(args...);
116+
}
117+
};
118+
119+
} // namespace scudo
120+
121+
#endif // SCUDO_MEM_MAP_BASE_H_

compiler-rt/lib/scudo/standalone/primary64.h

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "common.h"
1414
#include "list.h"
1515
#include "local_cache.h"
16+
#include "mem_map.h"
1617
#include "memtag.h"
1718
#include "options.h"
1819
#include "release.h"
@@ -103,10 +104,11 @@ template <typename Config> class SizeClassAllocator64 {
103104
SmallerBlockReleasePageDelta =
104105
PagesInGroup * (1 + MinSizeClass / 16U) / 100;
105106

106-
DCHECK_EQ(PrimaryBase, 0U);
107107
// Reserve the space required for the Primary.
108-
PrimaryBase = reinterpret_cast<uptr>(map(
109-
nullptr, PrimarySize, "scudo:primary_reserve", MAP_NOACCESS, &Data));
108+
CHECK(ReservedMemory.create(/*Addr=*/0U, PrimarySize,
109+
"scudo:primary_reserve"));
110+
PrimaryBase = ReservedMemory.getBase();
111+
DCHECK_NE(PrimaryBase, 0U);
110112

111113
u32 Seed;
112114
const u64 Time = getMonotonicTimeFast();
@@ -141,8 +143,7 @@ template <typename Config> class SizeClassAllocator64 {
141143
*Region = {};
142144
}
143145
if (PrimaryBase)
144-
unmap(reinterpret_cast<void *>(PrimaryBase), PrimarySize, UNMAP_ALL,
145-
&Data);
146+
ReservedMemory.release();
146147
PrimaryBase = 0U;
147148
}
148149

@@ -449,7 +450,7 @@ template <typename Config> class SizeClassAllocator64 {
449450
uptr AllocatedUser GUARDED_BY(Mutex) = 0;
450451
// The minimum size of pushed blocks to trigger page release.
451452
uptr TryReleaseThreshold GUARDED_BY(Mutex) = 0;
452-
MapPlatformData Data GUARDED_BY(Mutex) = {};
453+
MemMapT MemMap = {};
453454
ReleaseToOsInfo ReleaseInfo GUARDED_BY(Mutex) = {};
454455
bool Exhausted GUARDED_BY(Mutex) = false;
455456
};
@@ -459,11 +460,13 @@ template <typename Config> class SizeClassAllocator64 {
459460
};
460461
static_assert(sizeof(RegionInfo) % SCUDO_CACHE_LINE_SIZE == 0, "");
461462

463+
// TODO: `PrimaryBase` can be obtained from ReservedMemory. This needs to be
464+
// deprecated.
462465
uptr PrimaryBase = 0;
466+
ReservedMemoryT ReservedMemory = {};
463467
// The minimum size of pushed blocks that we will try to release the pages in
464468
// that size class.
465469
uptr SmallerBlockReleasePageDelta = 0;
466-
MapPlatformData Data = {};
467470
atomic_s32 ReleaseToOsIntervalMs = {};
468471
alignas(SCUDO_CACHE_LINE_SIZE) RegionInfo RegionInfoArray[NumClasses];
469472

@@ -742,14 +745,18 @@ template <typename Config> class SizeClassAllocator64 {
742745
Region->Exhausted = true;
743746
return false;
744747
}
745-
if (MappedUser == 0)
746-
Region->Data = Data;
747-
if (UNLIKELY(!map(
748-
reinterpret_cast<void *>(RegionBeg + MappedUser), MapSize,
749-
"scudo:primary",
748+
// TODO: Consider allocating MemMap in init().
749+
if (!Region->MemMap.isAllocated()) {
750+
Region->MemMap = ReservedMemory.dispatch(
751+
getRegionBaseByClassId(ClassId), RegionSize);
752+
}
753+
DCHECK(Region->MemMap.isAllocated());
754+
755+
if (UNLIKELY(!Region->MemMap.remap(
756+
RegionBeg + MappedUser, MapSize, "scudo:primary",
750757
MAP_ALLOWNOMEM | MAP_RESIZABLE |
751-
(useMemoryTagging<Config>(Options.load()) ? MAP_MEMTAG : 0),
752-
&Region->Data))) {
758+
(useMemoryTagging<Config>(Options.load()) ? MAP_MEMTAG
759+
: 0)))) {
753760
return false;
754761
}
755762
Region->MappedUser += MapSize;
@@ -1082,7 +1089,8 @@ template <typename Config> class SizeClassAllocator64 {
10821089
const uptr ReleaseRangeSize = ReleaseEnd - ReleaseBase;
10831090
const uptr ReleaseOffset = ReleaseBase - Region->RegionBeg;
10841091

1085-
ReleaseRecorder Recorder(Region->RegionBeg, ReleaseOffset, &Region->Data);
1092+
RegionReleaseRecorder<MemMapT> Recorder(&Region->MemMap, Region->RegionBeg,
1093+
ReleaseOffset);
10861094
PageReleaseContext Context(BlockSize, /*NumberOfRegions=*/1U,
10871095
ReleaseRangeSize, ReleaseOffset);
10881096

0 commit comments

Comments
 (0)