early-access version 1995

This commit is contained in:
pineappleEA
2021-08-16 13:42:12 +02:00
parent 1c11ae4a45
commit 66b92b0ba8
95 changed files with 18941 additions and 675 deletions

View File

@@ -2,13 +2,9 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <atomic>
#include <chrono>
#include <climits>
#include <condition_variable>
#include <memory>
#include <mutex>
#include <thread>
#include <vector>
@@ -16,28 +12,173 @@
#include <windows.h> // For OutputDebugStringW
#endif
#include "common/assert.h"
#include "common/fs/file.h"
#include "common/fs/fs.h"
#include "common/fs/fs_paths.h"
#include "common/fs/path_util.h"
#include "common/literals.h"
#include "common/logging/backend.h"
#include "common/logging/log.h"
#include "common/logging/text_formatter.h"
#include "common/settings.h"
#ifdef _WIN32
#include "common/string_util.h"
#endif
#include "common/threadsafe_queue.h"
namespace Common::Log {
namespace {
/**
* Interface for logging backends.
*/
class Backend {
public:
virtual ~Backend() = default;
virtual void Write(const Entry& entry) = 0;
virtual void EnableForStacktrace() = 0;
virtual void Flush() = 0;
};
/**
* Backend that writes to stderr and with color
*/
class ColorConsoleBackend final : public Backend {
public:
explicit ColorConsoleBackend() = default;
~ColorConsoleBackend() override = default;
void Write(const Entry& entry) override {
if (enabled.load(std::memory_order_relaxed)) {
PrintColoredMessage(entry);
}
}
void Flush() override {
// stderr shouldn't be buffered
}
void EnableForStacktrace() override {
enabled = true;
}
void SetEnabled(bool enabled_) {
enabled = enabled_;
}
private:
std::atomic_bool enabled{false};
};
/**
* Backend that writes to a file passed into the constructor
*/
class FileBackend final : public Backend {
public:
explicit FileBackend(const std::filesystem::path& filename) {
auto old_filename = filename;
old_filename += ".old.txt";
// Existence checks are done within the functions themselves.
// We don't particularly care if these succeed or not.
static_cast<void>(FS::RemoveFile(old_filename));
static_cast<void>(FS::RenameFile(filename, old_filename));
file = std::make_unique<FS::IOFile>(filename, FS::FileAccessMode::Write,
FS::FileType::TextFile);
}
~FileBackend() override = default;
void Write(const Entry& entry) override {
if (!enabled) {
return;
}
bytes_written += file->WriteString(FormatLogMessage(entry).append(1, '\n'));
using namespace Common::Literals;
// Prevent logs from exceeding a set maximum size in the event that log entries are spammed.
const auto write_limit = Settings::values.extended_logging ? 1_GiB : 100_MiB;
const bool write_limit_exceeded = bytes_written > write_limit;
if (entry.log_level >= Level::Error || write_limit_exceeded) {
if (write_limit_exceeded) {
// Stop writing after the write limit is exceeded.
// Don't close the file so we can print a stacktrace if necessary
enabled = false;
}
file->Flush();
}
}
void Flush() override {
file->Flush();
}
void EnableForStacktrace() override {
enabled = true;
bytes_written = 0;
}
private:
std::unique_ptr<FS::IOFile> file;
bool enabled = true;
std::size_t bytes_written = 0;
};
/**
* Backend that writes to Visual Studio's output window
*/
class DebuggerBackend final : public Backend {
public:
explicit DebuggerBackend() = default;
~DebuggerBackend() override = default;
void Write(const Entry& entry) override {
#ifdef _WIN32
::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str());
#endif
}
void Flush() override {}
void EnableForStacktrace() override {}
};
bool initialization_in_progress_suppress_logging = false;
/**
* Static state as a singleton.
*/
class Impl {
public:
static Impl& Instance() {
static Impl backend;
return backend;
if (!instance) {
abort();
}
return *instance;
}
static void Initialize() {
if (instance) {
abort();
}
using namespace Common::FS;
initialization_in_progress_suppress_logging = true;
const auto& log_dir = GetYuzuPath(YuzuPath::LogDir);
void(CreateDir(log_dir));
Filter filter;
filter.ParseFilterString(Settings::values.log_filter.GetValue());
instance = std::unique_ptr<Impl, decltype(&Deleter)>(new Impl(log_dir / LOG_FILE, filter),
Deleter);
initialization_in_progress_suppress_logging = false;
}
Impl(const Impl&) = delete;
@@ -46,74 +187,54 @@ public:
Impl(Impl&&) = delete;
Impl& operator=(Impl&&) = delete;
void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num,
const char* function, std::string message) {
message_queue.Push(
CreateEntry(log_class, log_level, filename, line_num, function, std::move(message)));
}
void AddBackend(std::unique_ptr<Backend> backend) {
std::lock_guard lock{writing_mutex};
backends.push_back(std::move(backend));
}
void RemoveBackend(std::string_view backend_name) {
std::lock_guard lock{writing_mutex};
std::erase_if(backends, [&backend_name](const auto& backend) {
return backend_name == backend->GetName();
});
}
const Filter& GetGlobalFilter() const {
return filter;
}
void SetGlobalFilter(const Filter& f) {
filter = f;
}
Backend* GetBackend(std::string_view backend_name) {
const auto it =
std::find_if(backends.begin(), backends.end(),
[&backend_name](const auto& i) { return backend_name == i->GetName(); });
if (it == backends.end())
return nullptr;
return it->get();
void SetColorConsoleBackendEnabled(bool enabled) {
color_console_backend.SetEnabled(enabled);
}
void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num,
const char* function, std::string message) {
if (!filter.CheckMessage(log_class, log_level))
return;
const Entry& entry =
CreateEntry(log_class, log_level, filename, line_num, function, std::move(message));
message_queue.Push(entry);
}
private:
Impl() {
backend_thread = std::thread([&] {
Entry entry;
auto write_logs = [&](Entry& e) {
std::lock_guard lock{writing_mutex};
for (const auto& backend : backends) {
backend->Write(e);
}
};
while (true) {
entry = message_queue.PopWait();
if (entry.final_entry) {
break;
}
write_logs(entry);
}
// Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a
// case where a system is repeatedly spamming logs even on close.
const int MAX_LOGS_TO_WRITE = filter.IsDebug() ? INT_MAX : 100;
int logs_written = 0;
while (logs_written++ < MAX_LOGS_TO_WRITE && message_queue.Pop(entry)) {
write_logs(entry);
}
});
}
Impl(const std::filesystem::path& file_backend_filename, const Filter& filter_)
: filter{filter_}, file_backend{file_backend_filename}, backend_thread{std::thread([this] {
Common::SetCurrentThreadName("yuzu:Log");
Entry entry;
const auto write_logs = [this, &entry]() {
ForEachBackend([&entry](Backend& backend) { backend.Write(entry); });
};
while (true) {
entry = message_queue.PopWait();
if (entry.final_entry) {
break;
}
write_logs();
}
// Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a
// case where a system is repeatedly spamming logs even on close.
int max_logs_to_write = filter.IsDebug() ? INT_MAX : 100;
while (max_logs_to_write-- && message_queue.Pop(entry)) {
write_logs();
}
})} {}
~Impl() {
Entry entry;
entry.final_entry = true;
message_queue.Push(entry);
StopBackendThread();
}
void StopBackendThread() {
Entry stop_entry{};
stop_entry.final_entry = true;
message_queue.Push(stop_entry);
backend_thread.join();
}
@@ -135,100 +256,51 @@ private:
};
}
std::mutex writing_mutex;
std::thread backend_thread;
std::vector<std::unique_ptr<Backend>> backends;
MPSCQueue<Entry> message_queue;
void ForEachBackend(auto lambda) {
lambda(static_cast<Backend&>(debugger_backend));
lambda(static_cast<Backend&>(color_console_backend));
lambda(static_cast<Backend&>(file_backend));
}
static void Deleter(Impl* ptr) {
delete ptr;
}
static inline std::unique_ptr<Impl, decltype(&Deleter)> instance{nullptr, Deleter};
Filter filter;
DebuggerBackend debugger_backend{};
ColorConsoleBackend color_console_backend{};
FileBackend file_backend;
std::thread backend_thread;
MPSCQueue<Entry> message_queue{};
std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
};
} // namespace
ConsoleBackend::~ConsoleBackend() = default;
void ConsoleBackend::Write(const Entry& entry) {
PrintMessage(entry);
void Initialize() {
Impl::Initialize();
}
ColorConsoleBackend::~ColorConsoleBackend() = default;
void ColorConsoleBackend::Write(const Entry& entry) {
PrintColoredMessage(entry);
}
FileBackend::FileBackend(const std::filesystem::path& filename) {
auto old_filename = filename;
old_filename += ".old.txt";
// Existence checks are done within the functions themselves.
// We don't particularly care if these succeed or not.
FS::RemoveFile(old_filename);
void(FS::RenameFile(filename, old_filename));
file =
std::make_unique<FS::IOFile>(filename, FS::FileAccessMode::Write, FS::FileType::TextFile);
}
FileBackend::~FileBackend() = default;
void FileBackend::Write(const Entry& entry) {
if (!file->IsOpen()) {
return;
}
using namespace Common::Literals;
// Prevent logs from exceeding a set maximum size in the event that log entries are spammed.
constexpr std::size_t MAX_BYTES_WRITTEN = 100_MiB;
constexpr std::size_t MAX_BYTES_WRITTEN_EXTENDED = 1_GiB;
const bool write_limit_exceeded =
bytes_written > MAX_BYTES_WRITTEN_EXTENDED ||
(bytes_written > MAX_BYTES_WRITTEN && !Settings::values.extended_logging);
// Close the file after the write limit is exceeded.
if (write_limit_exceeded) {
file->Close();
return;
}
bytes_written += file->WriteString(FormatLogMessage(entry).append(1, '\n'));
if (entry.log_level >= Level::Error) {
file->Flush();
}
}
DebuggerBackend::~DebuggerBackend() = default;
void DebuggerBackend::Write(const Entry& entry) {
#ifdef _WIN32
::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str());
#endif
void DisableLoggingInTests() {
initialization_in_progress_suppress_logging = true;
}
void SetGlobalFilter(const Filter& filter) {
Impl::Instance().SetGlobalFilter(filter);
}
void AddBackend(std::unique_ptr<Backend> backend) {
Impl::Instance().AddBackend(std::move(backend));
}
void RemoveBackend(std::string_view backend_name) {
Impl::Instance().RemoveBackend(backend_name);
}
Backend* GetBackend(std::string_view backend_name) {
return Impl::Instance().GetBackend(backend_name);
void SetColorConsoleBackendEnabled(bool enabled) {
Impl::Instance().SetColorConsoleBackendEnabled(enabled);
}
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
unsigned int line_num, const char* function, const char* format,
const fmt::format_args& args) {
auto& instance = Impl::Instance();
const auto& filter = instance.GetGlobalFilter();
if (!filter.CheckMessage(log_class, log_level))
return;
instance.PushEntry(log_class, log_level, filename, line_num, function,
fmt::vformat(format, args));
if (!initialization_in_progress_suppress_logging) {
Impl::Instance().PushEntry(log_class, log_level, filename, line_num, function,
fmt::vformat(format, args));
}
}
} // namespace Common::Log

View File

@@ -5,120 +5,21 @@
#pragma once
#include <filesystem>
#include <memory>
#include <string>
#include <string_view>
#include "common/logging/filter.h"
#include "common/logging/log.h"
namespace Common::FS {
class IOFile;
}
namespace Common::Log {
class Filter;
/**
* Interface for logging backends. As loggers can be created and removed at runtime, this can be
* used by a frontend for adding a custom logging backend as needed
*/
class Backend {
public:
virtual ~Backend() = default;
/// Initializes the logging system. This should be the first thing called in main.
void Initialize();
virtual void SetFilter(const Filter& new_filter) {
filter = new_filter;
}
virtual const char* GetName() const = 0;
virtual void Write(const Entry& entry) = 0;
private:
Filter filter;
};
void DisableLoggingInTests();
/**
* Backend that writes to stderr without any color commands
*/
class ConsoleBackend : public Backend {
public:
~ConsoleBackend() override;
static const char* Name() {
return "console";
}
const char* GetName() const override {
return Name();
}
void Write(const Entry& entry) override;
};
/**
* Backend that writes to stderr and with color
*/
class ColorConsoleBackend : public Backend {
public:
~ColorConsoleBackend() override;
static const char* Name() {
return "color_console";
}
const char* GetName() const override {
return Name();
}
void Write(const Entry& entry) override;
};
/**
* Backend that writes to a file passed into the constructor
*/
class FileBackend : public Backend {
public:
explicit FileBackend(const std::filesystem::path& filename);
~FileBackend() override;
static const char* Name() {
return "file";
}
const char* GetName() const override {
return Name();
}
void Write(const Entry& entry) override;
private:
std::unique_ptr<FS::IOFile> file;
std::size_t bytes_written = 0;
};
/**
* Backend that writes to Visual Studio's output window
*/
class DebuggerBackend : public Backend {
public:
~DebuggerBackend() override;
static const char* Name() {
return "debugger";
}
const char* GetName() const override {
return Name();
}
void Write(const Entry& entry) override;
};
void AddBackend(std::unique_ptr<Backend> backend);
void RemoveBackend(std::string_view backend_name);
Backend* GetBackend(std::string_view backend_name);
/**
* The global filter will prevent any messages from even being processed if they are filtered. Each
* backend can have a filter, but if the level is lower than the global filter, the backend will
* never get the message
* The global filter will prevent any messages from even being processed if they are filtered.
*/
void SetGlobalFilter(const Filter& filter);
} // namespace Common::Log
void SetColorConsoleBackendEnabled(bool enabled);
} // namespace Common::Log

View File

@@ -4,175 +4,242 @@
#pragma once
// a simple lockless thread-safe,
// single reader, single writer queue
#include <atomic>
#include <condition_variable>
#include <cstddef>
#include <iostream>
#include <mutex>
#include <utility>
#include <optional>
namespace Common {
/// a more foolproof multiple reader, multiple writer queue
template <typename T>
class SPSCQueue {
class MPMCQueue {
#define ABORT() \
do { \
std::cerr << __FILE__ " ERR " << __LINE__ << std::endl; \
abort(); \
} while (0)
public:
SPSCQueue() {
write_ptr = read_ptr = new ElementPtr();
}
~SPSCQueue() {
// this will empty out the whole queue
delete read_ptr;
}
[[nodiscard]] std::size_t Size() const {
return size.load();
}
[[nodiscard]] bool Empty() const {
return Size() == 0;
}
[[nodiscard]] T& Front() const {
return read_ptr->current;
~MPMCQueue() {
Clear();
if (waiting || head || tail) {
// Remove all the ABORT() after 1 month merged without problems
ABORT();
}
}
template <typename Arg>
void Push(Arg&& t) {
// create the element, add it to the queue
write_ptr->current = std::forward<Arg>(t);
// set the next pointer to a new element ptr
// then advance the write pointer
ElementPtr* new_ptr = new ElementPtr();
write_ptr->next.store(new_ptr, std::memory_order_release);
write_ptr = new_ptr;
++size;
// cv_mutex must be held or else there will be a missed wakeup if the other thread is in the
// line before cv.wait
// TODO(bunnei): This can be replaced with C++20 waitable atomics when properly supported.
// See discussion on https://github.com/yuzu-emu/yuzu/pull/3173 for details.
std::lock_guard lock{cv_mutex};
cv.notify_one();
}
void Pop() {
--size;
ElementPtr* tmpptr = read_ptr;
// advance the read pointer
read_ptr = tmpptr->next.load();
// set the next element to nullptr to stop the recursive deletion
tmpptr->next.store(nullptr);
delete tmpptr; // this also deletes the element
}
bool Pop(T& t) {
if (Empty())
return false;
--size;
ElementPtr* tmpptr = read_ptr;
read_ptr = tmpptr->next.load(std::memory_order_acquire);
t = std::move(tmpptr->current);
tmpptr->next.store(nullptr);
delete tmpptr;
return true;
}
void Wait() {
if (Empty()) {
std::unique_lock lock{cv_mutex};
cv.wait(lock, [this]() { return !Empty(); });
Node* const node = new Node(std::forward<Arg>(t));
if (!node || node == PLACEHOLDER) {
ABORT();
}
while (true) {
if (Node* const previous = tail.load(ACQUIRE)) {
if (Node* exchange = nullptr;
!previous->next.compare_exchange_weak(exchange, node, ACQ_REL)) {
continue;
}
if (tail.exchange(node, ACQ_REL) != previous) {
ABORT();
}
} else {
if (Node* exchange = nullptr;
!tail.compare_exchange_weak(exchange, node, ACQ_REL)) {
continue;
}
for (Node* exchange = nullptr;
!head.compare_exchange_weak(exchange, node, ACQ_REL);)
;
}
break;
}
if (waiting.load(ACQUIRE)) {
std::lock_guard lock{mutex};
condition.notify_one();
}
}
bool Pop(T& t) {
return PopImpl<false>(t);
}
T PopWait() {
Wait();
T t;
Pop(t);
if (!PopImpl<true>(t)) {
ABORT();
}
return t;
}
// not thread-safe
void Wait() {
if (head.load(ACQUIRE)) {
return;
}
static_cast<void>(waiting.fetch_add(1, ACQ_REL));
std::unique_lock lock{mutex};
while (true) {
if (head.load(ACQUIRE)) {
break;
}
condition.wait(lock);
}
if (!waiting.fetch_sub(1, ACQ_REL)) {
ABORT();
}
}
void Clear() {
size.store(0);
delete read_ptr;
write_ptr = read_ptr = new ElementPtr();
while (true) {
Node* const last = tail.load(ACQUIRE);
if (!last) {
return;
}
if (Node* exchange = nullptr;
!last->next.compare_exchange_weak(exchange, PLACEHOLDER, ACQ_REL)) {
continue;
}
if (tail.exchange(nullptr, ACQ_REL) != last) {
ABORT();
}
Node* node = head.exchange(nullptr, ACQ_REL);
while (node && node != PLACEHOLDER) {
Node* next = node->next.load(ACQUIRE);
delete node;
node = next;
}
return;
}
}
private:
// stores a pointer to element
// and a pointer to the next ElementPtr
class ElementPtr {
public:
ElementPtr() {}
~ElementPtr() {
ElementPtr* next_ptr = next.load();
if (next_ptr)
delete next_ptr;
template <bool WAIT>
bool PopImpl(T& t) {
std::optional<std::unique_lock<std::mutex>> lock{std::nullopt};
while (true) {
Node* const node = head.load(ACQUIRE);
if (!node) {
if constexpr (!WAIT) {
return false;
}
if (!lock) {
static_cast<void>(waiting.fetch_add(1, ACQ_REL));
lock = std::unique_lock{mutex};
continue;
}
condition.wait(*lock);
continue;
}
Node* const next = node->next.load(ACQUIRE);
if (next) {
if (next == PLACEHOLDER) {
continue;
}
if (Node* exchange = node; !head.compare_exchange_weak(exchange, next, ACQ_REL)) {
continue;
}
} else {
if (Node* exchange = nullptr;
!node->next.compare_exchange_weak(exchange, PLACEHOLDER, ACQ_REL)) {
continue;
}
if (tail.exchange(nullptr, ACQ_REL) != node) {
ABORT();
}
if (head.exchange(nullptr, ACQ_REL) != node) {
ABORT();
}
}
t = std::move(node->value);
delete node;
if (lock) {
if (!waiting.fetch_sub(1, ACQ_REL)) {
ABORT();
}
}
return true;
}
}
T current;
std::atomic<ElementPtr*> next{nullptr};
struct Node {
template <typename Arg>
explicit Node(Arg&& t) : value{std::forward<Arg>(t)} {}
Node(const Node&) = delete;
Node& operator=(const Node&) = delete;
Node(Node&&) = delete;
Node& operator=(Node&&) = delete;
const T value;
std::atomic<Node*> next{nullptr};
};
ElementPtr* write_ptr;
ElementPtr* read_ptr;
std::atomic_size_t size{0};
std::mutex cv_mutex;
std::condition_variable cv;
// We only need to avoid SEQ_CST on X86
// We can add RELAXED later if we port to ARM and it's too slow
static constexpr auto ACQUIRE = std::memory_order_acquire;
static constexpr auto ACQ_REL = std::memory_order_acq_rel;
static inline const auto PLACEHOLDER = reinterpret_cast<Node*>(1);
std::atomic<Node*> head{nullptr};
std::atomic<Node*> tail{nullptr};
std::atomic_size_t waiting{0};
std::condition_variable condition{};
std::mutex mutex{};
#undef ABORT
};
// a simple thread-safe,
// single reader, multiple writer queue
/// a simple lockless thread-safe,
/// single reader, single writer queue
template <typename T>
class MPSCQueue {
class /*[[deprecated("Transition to MPMCQueue")]]*/ SPSCQueue {
public:
[[nodiscard]] std::size_t Size() const {
return spsc_queue.Size();
}
[[nodiscard]] bool Empty() const {
return spsc_queue.Empty();
}
[[nodiscard]] T& Front() const {
return spsc_queue.Front();
}
template <typename Arg>
void Push(Arg&& t) {
std::lock_guard lock{write_lock};
spsc_queue.Push(t);
}
void Pop() {
return spsc_queue.Pop();
queue.Push(std::forward<Arg>(t));
}
bool Pop(T& t) {
return spsc_queue.Pop(t);
return queue.Pop(t);
}
void Wait() {
spsc_queue.Wait();
queue.Wait();
}
T PopWait() {
return spsc_queue.PopWait();
return queue.PopWait();
}
// not thread-safe
void Clear() {
spsc_queue.Clear();
queue.Clear();
}
private:
SPSCQueue<T> spsc_queue;
std::mutex write_lock;
MPMCQueue<T> queue{};
};
/// a simple thread-safe,
/// single reader, multiple writer queue
template <typename T>
class /*[[deprecated("Transition to MPMCQueue")]]*/ MPSCQueue {
public:
template <typename Arg>
void Push(Arg&& t) {
queue.Push(std::forward<Arg>(t));
}
bool Pop(T& t) {
return queue.Pop(t);
}
T PopWait() {
return queue.PopWait();
}
private:
MPMCQueue<T> queue{};
};
} // namespace Common

View File

@@ -6,7 +6,7 @@
#include <bitset>
#include <initializer_list>
#include <xbyak.h>
#include <xbyak/xbyak.h>
#include "common/assert.h"
namespace Common::X64 {

View File

@@ -5,7 +5,7 @@
#pragma once
#include <type_traits>
#include <xbyak.h>
#include <xbyak/xbyak.h>
#include "common/x64/xbyak_abi.h"
namespace Common::X64 {