Просмотр исходного кода

Common: implement MultiLevelPageTable.

Fernando Sahmkow 4 лет назад
Родитель
Сommit
b617874724

+ 2 - 0
src/common/CMakeLists.txt

@@ -81,6 +81,8 @@ add_library(common STATIC
     microprofile.cpp
     microprofile.h
     microprofileui.h
+    multi_level_page_table.cpp
+    multi_level_page_table.h
     nvidia_flags.cpp
     nvidia_flags.h
     page_table.cpp

+ 7 - 0
src/common/multi_level_page_table.cpp

@@ -0,0 +1,7 @@
+#include "common/multi_level_page_table.inc"
+
+namespace Common {
+template class Common::MultiLevelPageTable<GPUVAddr>;
+template class Common::MultiLevelPageTable<VAddr>;
+template class Common::MultiLevelPageTable<PAddr>;
+} // namespace Common

+ 79 - 0
src/common/multi_level_page_table.h

@@ -0,0 +1,79 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "common/common_types.h"
+
+namespace Common {
+
+template <typename BaseAddr>
+class MultiLevelPageTable final {
+public:
+    constexpr MultiLevelPageTable() = default;
+    explicit MultiLevelPageTable(std::size_t address_space_bits, std::size_t first_level_bits,
+                                 std::size_t page_bits);
+
+    ~MultiLevelPageTable() noexcept;
+
+    MultiLevelPageTable(const MultiLevelPageTable&) = delete;
+    MultiLevelPageTable& operator=(const MultiLevelPageTable&) = delete;
+
+    MultiLevelPageTable(MultiLevelPageTable&& other) noexcept
+        : address_space_bits{std::exchange(other.address_space_bits, 0)},
+          first_level_bits{std::exchange(other.first_level_bits, 0)}, page_bits{std::exchange(
+                                                                          other.page_bits, 0)},
+          first_level_shift{std::exchange(other.first_level_shift, 0)},
+          first_level_chunk_size{std::exchange(other.first_level_chunk_size, 0)},
+          first_level_map{std::move(other.first_level_map)}, base_ptr{std::exchange(other.base_ptr,
+                                                                                    nullptr)} {}
+
+    MultiLevelPageTable& operator=(MultiLevelPageTable&& other) noexcept {
+        address_space_bits = std::exchange(other.address_space_bits, 0);
+        first_level_bits = std::exchange(other.first_level_bits, 0);
+        page_bits = std::exchange(other.page_bits, 0);
+        first_level_shift = std::exchange(other.first_level_shift, 0);
+        first_level_chunk_size = std::exchange(other.first_level_chunk_size, 0);
+        alloc_size = std::exchange(other.alloc_size, 0);
+        first_level_map = std::move(other.first_level_map);
+        base_ptr = std::exchange(other.base_ptr, nullptr);
+        return *this;
+    }
+
+    void ReserveRange(u64 start, std::size_t size);
+
+    [[nodiscard]] constexpr const BaseAddr& operator[](std::size_t index) const {
+        return base_ptr[index];
+    }
+
+    [[nodiscard]] constexpr BaseAddr& operator[](std::size_t index) {
+        return base_ptr[index];
+    }
+
+    [[nodiscard]] constexpr BaseAddr* data() {
+        return base_ptr;
+    }
+
+    [[nodiscard]] constexpr const BaseAddr* data() const {
+        return base_ptr;
+    }
+
+private:
+    void AllocateLevel(u64 level);
+
+    std::size_t address_space_bits{};
+    std::size_t first_level_bits{};
+    std::size_t page_bits{};
+    std::size_t first_level_shift{};
+    std::size_t first_level_chunk_size{};
+    std::size_t alloc_size{};
+    std::vector<void*> first_level_map{};
+    BaseAddr* base_ptr{};
+};
+
+} // namespace Common

+ 83 - 0
src/common/multi_level_page_table.inc

@@ -0,0 +1,83 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#ifdef _WIN32
+#include <windows.h>
+#else
+#include <sys/mman.h>
+#endif
+
+#include "common/assert.h"
+#include "common/multi_level_page_table.h"
+
+namespace Common {
+
+template <typename BaseAddr>
+MultiLevelPageTable<BaseAddr>::MultiLevelPageTable(std::size_t address_space_bits_,
+                                                   std::size_t first_level_bits_,
+                                                   std::size_t page_bits_)
+    : address_space_bits{address_space_bits_},
+      first_level_bits{first_level_bits_}, page_bits{page_bits_} {
+    first_level_shift = address_space_bits - first_level_bits;
+    first_level_chunk_size = 1ULL << (first_level_shift - page_bits);
+    alloc_size = (1ULL << (address_space_bits - page_bits)) * sizeof(BaseAddr);
+    std::size_t first_level_size = 1ULL << first_level_bits;
+    first_level_map.resize(first_level_size, nullptr);
+#ifdef _WIN32
+    void* base{VirtualAlloc(nullptr, alloc_size, MEM_RESERVE, PAGE_READWRITE)};
+#else
+    void* base{mmap(nullptr, alloc_size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)};
+
+    if (base == MAP_FAILED) {
+        base = nullptr;
+    }
+#endif
+
+    ASSERT(base);
+    base_ptr = reinterpret_cast<BaseAddr*>(base);
+}
+
+template <typename BaseAddr>
+MultiLevelPageTable<BaseAddr>::~MultiLevelPageTable() noexcept {
+    if (!base_ptr) {
+        return;
+    }
+#ifdef _WIN32
+    ASSERT(VirtualFree(base_ptr, 0, MEM_RELEASE));
+#else
+    ASSERT(munmap(base_ptr, alloc_size) == 0);
+#endif
+}
+
+template <typename BaseAddr>
+void MultiLevelPageTable<BaseAddr>::ReserveRange(u64 start, std::size_t size) {
+    const u64 new_start = start >> first_level_shift;
+    const u64 new_end =
+        (start + size + (first_level_chunk_size << page_bits) - 1) >> first_level_shift;
+    for (u64 i = new_start; i <= new_end; i++) {
+        if (!first_level_map[i]) {
+            AllocateLevel(i);
+        }
+    }
+}
+
+template <typename BaseAddr>
+void MultiLevelPageTable<BaseAddr>::AllocateLevel(u64 level) {
+    void* ptr = reinterpret_cast<char*>(base_ptr) + level * first_level_chunk_size;
+#ifdef _WIN32
+    void* base{VirtualAlloc(ptr, first_level_chunk_size, MEM_COMMIT, PAGE_READWRITE)};
+#else
+    void* base{mmap(ptr, first_level_chunk_size, PROT_READ | PROT_WRITE,
+                    MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)};
+
+    if (base == MAP_FAILED) {
+        base = nullptr;
+    }
+#endif
+    ASSERT(base);
+
+    first_level_map[level] = base;
+}
+
+} // namespace Common