#pragma once

/*
* Wrapper over Windows virtual memory API.
* Used to allocate executable memory for JIT-compiled programs.
*/

#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>

#include <cstdint>
#include <format>

#include "aliases.hpp"
#include "exception.hpp"
#include "heaparray.hpp"

namespace modernRX {
    template<typename T>
    inline constexpr bool is_vector_v = std::same_as<T, std::vector<typename T::value_type, typename T::allocator_type> >;

    template<typename T>
    inline constexpr bool is_spanable_v = std::is_constructible_v<const_span<typename T::value_type>, T>;

    template<typename T>
    requires is_spanable_v<T>
    [[nodiscard]] constexpr const_span<typename T::value_type> as_span(const T& t) noexcept {
        return const_span<typename T::value_type>(t);
    }

    // Allocates executable memory and returns a function pointer to it.
    // Type of the function pointer is specified by the template parameter.
    // The function pointer is wrapped in a unique_ptr with a custom deleter that will free allocated memory and data associated with program at destruction.
    // May throw if memory allocation or protection fails.
    // 
    // Function takes data as a parameter and prolongs its lifetime until the JIT-compiled function is destroyed.
    // Somewhat hacky, would be nice to find a better solution.
    template<typename Fn, typename Code, typename Data>
    requires is_vector_v<Data>
    [[nodiscard]] constexpr jit_function_ptr<Fn> makeExecutable(const Code&& code, Data&& data) {
        const auto code_size{ as_span(code).size_bytes() };

        // Alloc buffer for writing code.
        auto buffer{ VirtualAlloc(nullptr, code_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE) };
        if (buffer == nullptr) {
            throw Exception(std::format("Failed to allocate memory with error: {:d}", GetLastError()));
        }

        std::memcpy(buffer, code.data(), code_size);

        // Protect from writing, but make code executable.
        DWORD dummy{};
        if (!VirtualProtect(buffer, code_size, PAGE_EXECUTE_READ, &dummy)) {
            const auto err{ GetLastError() };
            VirtualFree(buffer, 0, MEM_RELEASE); // Ignore error.
            throw Exception(std::format("Failed to protect memory with error: {:d}", err));
        }

        return jit_function_ptr<Fn>(reinterpret_cast<Fn*>(buffer), [moved_data = std::move(data)](Fn* ptr) noexcept {
            VirtualFree(ptr, 0, MEM_RELEASE); // Ignore error.
            // moved_data will be destroyed and release memory here automatically.
        });
    }

    // Allocates executable memory that is also writeable (not protected).
    // Use for performance when code buffers are allocated and changed frequently.
    // May throw if memory allocation fails.
    template<typename Fn>
    [[nodiscard]] constexpr jit_function_ptr<Fn> makeExecutable(const size_t code_size) {
        // Alloc buffer for writing code.
        const auto buffer{ VirtualAlloc(nullptr, code_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE) };
        if (buffer == nullptr) {
            throw Exception(std::format("Failed to allocate memory with error: {:d}", GetLastError()));
        }

        return jit_function_ptr<Fn>(reinterpret_cast<Fn*>(buffer), [](Fn* ptr) noexcept {
            VirtualFree(ptr, 0, MEM_RELEASE); // Ignore error.           
        });
    }
}

Generated by OpenCppCoverage (Version: 0.9.9.0)