summaryrefslogtreecommitdiff
path: root/Source/Core/Common/MemArena.h
blob: d9d472079cd667ccd8ec99481311db35f8c05ec5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
// Copyright 2008 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <cstddef>
#include <string_view>
#include <vector>

#ifdef __APPLE__
#include <mach/mach.h>
#endif

#include "Common/CommonTypes.h"
#include "Common/DynamicLibrary.h"

namespace Common
{
#ifdef _WIN32
struct WindowsMemoryRegion;

struct WindowsMemoryFunctions
{
  Common::DynamicLibrary m_kernel32_handle;
  Common::DynamicLibrary m_api_ms_win_core_memory_l1_1_6_handle;
  void* m_address_UnmapViewOfFileEx = nullptr;
  void* m_address_VirtualAlloc2 = nullptr;
  void* m_address_MapViewOfFile3 = nullptr;
};
#endif

// This class lets you create a block of anonymous RAM, and then arbitrarily map views into it.
// Multiple views can mirror the same section of the block, which makes it very convenient for
// emulating memory mirrors.
class MemArena final
{
public:
  MemArena();
  ~MemArena();
  MemArena(const MemArena&) = delete;
  MemArena(MemArena&&) = delete;
  MemArena& operator=(const MemArena&) = delete;
  MemArena& operator=(MemArena&&) = delete;

  ///
  /// Allocate the singular memory segment handled by this MemArena. This will be the actual
  /// 'physical' available memory for this arena. After allocation, it can be interacted with using
  /// CreateView() and ReleaseView(). Used to make a mappable region for emulated memory.
  ///
  /// @param size The amount of bytes that should be allocated in this region.
  /// @param base_name A base name for the shared memory region, if applicable for this platform.
  /// Will be extended with the process ID.
  ///
  void GrabSHMSegment(size_t size, std::string_view base_name);

  ///
  /// Release the memory segment previously allocated with GrabSHMSegment().
  /// Should not be called before all views have been released.
  ///
  void ReleaseSHMSegment();

  ///
  /// Map a memory region in the memory segment previously allocated with GrabSHMSegment().
  ///
  /// @param offset Offset within the memory segment to map at.
  /// @param size Size of the region to map.
  ///
  /// @return Pointer to the memory region, or nullptr on failure.
  ///
  void* CreateView(s64 offset, size_t size);

  ///
  /// Unmap a memory region previously mapped with CreateView().
  /// Should not be called on a view that is still mapped into the virtual memory region.
  ///
  /// @param view Pointer returned by CreateView().
  /// @param size Size passed to the corresponding CreateView() call.
  ///
  void ReleaseView(void* view, size_t size);

  ///
  /// Reserve the singular 'virtual' memory region handled by this MemArena. This is used to create
  /// our 'fastmem' memory area for the emulated game code to access directly.
  ///
  /// @param memory_size Size in bytes of the memory region to reserve.
  ///
  /// @return Pointer to the memory region, or nullptr on failure.
  ///
  u8* ReserveMemoryRegion(size_t memory_size);

  ///
  /// Release the memory region previously reserved with ReserveMemoryRegion().
  /// Should not be called while any memory region is still mapped.
  ///
  void ReleaseMemoryRegion();

  ///
  /// Map a section from the memory segment previously allocated with GrabSHMSegment()
  /// into the region previously reserved with ReserveMemoryRegion().
  ///
  /// @param offset Offset within the memory segment previous allocated by GrabSHMSegment() to map
  /// from.
  /// @param size Size of the region to map.
  /// @param base Address within the memory region from ReserveMemoryRegion() where to map it.
  ///
  /// @return The address we actually ended up mapping, which should be the given 'base'.
  ///
  void* MapInMemoryRegion(s64 offset, size_t size, void* base);

  ///
  /// Unmap a memory region previously mapped with MapInMemoryRegion().
  ///
  /// @param view Pointer returned by MapInMemoryRegion().
  /// @param size Size passed to the corresponding MapInMemoryRegion() call.
  ///
  void UnmapFromMemoryRegion(void* view, size_t size);

private:
#ifdef _WIN32
  WindowsMemoryRegion* EnsureSplitRegionForMapping(void* address, size_t size);
  bool JoinRegionsAfterUnmap(void* address, size_t size);

  std::vector<WindowsMemoryRegion> m_regions;
  void* m_reserved_region = nullptr;
  void* m_memory_handle = nullptr;
  WindowsMemoryFunctions m_memory_functions;
#elif defined(__APPLE__)
  vm_address_t m_shm_address = 0;
  vm_size_t m_shm_size = 0;
  mem_entry_name_port_t m_shm_entry = MACH_PORT_NULL;

  vm_address_t m_region_address = 0;
  vm_size_t m_region_size = 0;
#else
  int m_shm_fd = 0;
  void* m_reserved_region = nullptr;
  std::size_t m_reserved_region_size = 0;
#endif
};

// This class represents a single fixed-size memory region where the individual memory pages are
// only actually allocated on first access. The memory will be zero on first access.
class LazyMemoryRegion final
{
public:
  LazyMemoryRegion();
  ~LazyMemoryRegion();
  LazyMemoryRegion(const LazyMemoryRegion&) = delete;
  LazyMemoryRegion(LazyMemoryRegion&&) = delete;
  LazyMemoryRegion& operator=(const LazyMemoryRegion&) = delete;
  LazyMemoryRegion& operator=(LazyMemoryRegion&&) = delete;

  ///
  /// Reserve a memory region.
  ///
  /// @param size The size of the region.
  ///
  /// @return The address the region was mapped at. Returns nullptr on failure.
  ///
  void* Create(size_t size);

  ///
  /// Reset the memory region back to zero, throwing away any mapped pages.
  /// This can only be called after a successful call to Create().
  ///
  void Clear();

  ///
  /// Release the memory previously reserved with Create(). After this call the pointer that was
  /// returned by Create() will become invalid.
  ///
  void Release();

  ///
  /// Ensure that the memory page at the given byte offset from the start of the memory region is
  /// writable. We use this on Windows as a workaround to only actually commit pages as they are
  /// written to. On other OSes this does nothing.
  ///
  /// @param offset The offset into the memory region that should be made writable if it isn't.
  ///
  void EnsureMemoryPageWritable(size_t offset)
  {
#ifdef _WIN32
    const size_t block_index = offset / BLOCK_SIZE;
    if (m_writable_block_handles[block_index] == nullptr)
      MakeMemoryBlockWritable(block_index);
#endif
  }

private:
  void* m_memory = nullptr;
  size_t m_size = 0;

#ifdef _WIN32
  void* m_zero_block = nullptr;
  constexpr static size_t BLOCK_SIZE = 8 * 1024 * 1024;  // size of allocated memory blocks
  WindowsMemoryFunctions m_memory_functions;
  std::vector<void*> m_writable_block_handles;

  void MakeMemoryBlockWritable(size_t offset);
#endif
};

}  // namespace Common