mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-06-06 01:13:45 -04:00
Compare commits
7 commits
4b4dca7be9
...
429a7f6fe9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
429a7f6fe9 | ||
|
|
dab20371a2 | ||
|
|
16ba4cb997 | ||
|
|
ffc7910cd3 | ||
|
|
aadcc24aac | ||
|
|
89199f4d27 | ||
|
|
978d9d935d |
75 changed files with 1737 additions and 234 deletions
|
|
@ -306,7 +306,7 @@ if (YUZU_ROOM)
|
|||
add_compile_definitions(YUZU_ROOM)
|
||||
endif()
|
||||
|
||||
if ((ANDROID OR APPLE OR UNIX) AND (NOT PLATFORM_LINUX OR ANDROID) AND NOT WIN32)
|
||||
if (UNIX AND NOT (PLATFORM_LINUX OR WIN32))
|
||||
if(CXX_APPLE OR CXX_CLANG)
|
||||
# libc++ has stop_token and jthread as experimental
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexperimental-library")
|
||||
|
|
@ -524,6 +524,8 @@ elseif (WIN32)
|
|||
# PSAPI is the Process Status API
|
||||
set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} psapi imm32 version crypt32 rpcrt4 gdi32 wldap32 mswsock)
|
||||
endif()
|
||||
elseif (PLATFORM_MANAGARM)
|
||||
set(PLATFORM_LIBRARIES iconv intl)
|
||||
elseif (PLATFORM_HAIKU)
|
||||
# Haiku is so special :)
|
||||
set(PLATFORM_LIBRARIES bsd /boot/system/lib/libnetwork.so)
|
||||
|
|
|
|||
|
|
@ -303,7 +303,7 @@ namespace {
|
|||
}
|
||||
[[nodiscard]] s64 GetHostCNTFRQ() noexcept {
|
||||
u64 cntfrq_el0 = 0;
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
std::string_view board{""};
|
||||
char buffer[PROP_VALUE_MAX];
|
||||
int len{__system_property_get("ro.product.board", buffer)};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
|
||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
|
@ -30,7 +33,7 @@ std::string NativeErrorToString(int e) {
|
|||
return ret;
|
||||
#else
|
||||
char err_str[255];
|
||||
#if defined(ANDROID) || \
|
||||
#if defined(__ANDROID__) || \
|
||||
(defined(__GLIBC__) && (_GNU_SOURCE || (_POSIX_C_SOURCE < 200112L && _XOPEN_SOURCE < 600)))
|
||||
// Thread safe (GNU-specific)
|
||||
const char* str = strerror_r(e, err_str, sizeof(err_str));
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
#include "common/assert.h"
|
||||
#include "common/fs/file.h"
|
||||
#include "common/fs/fs.h"
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
#include "common/fs/fs_android.h"
|
||||
#endif
|
||||
#include "common/logging.h"
|
||||
|
|
@ -259,7 +259,7 @@ void IOFile::Open(const fs::path& path, FileAccessMode mode, FileType type, File
|
|||
} else {
|
||||
_wfopen_s(&file, path.c_str(), AccessModeToWStr(mode, type));
|
||||
}
|
||||
#elif ANDROID
|
||||
#elif __ANDROID__
|
||||
if (Android::IsContentUri(path)) {
|
||||
ASSERT_MSG(mode == FileAccessMode::Read, "Content URI file access is for read-only!");
|
||||
const auto fd = Android::OpenContentUri(path, Android::OpenMode::Read);
|
||||
|
|
@ -396,7 +396,7 @@ u64 IOFile::GetSize() const {
|
|||
// Flush any unwritten buffered data into the file prior to retrieving the file size.
|
||||
std::fflush(file);
|
||||
|
||||
#if ANDROID
|
||||
#ifdef __ANDROID__
|
||||
u64 file_size = 0;
|
||||
if (Android::IsContentUri(file_path)) {
|
||||
file_size = Android::GetSize(file_path);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
#include "common/fs/file.h"
|
||||
#include "common/fs/fs.h"
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
#include "common/fs/fs_android.h"
|
||||
#endif
|
||||
#include "common/fs/path_util.h"
|
||||
|
|
@ -532,7 +532,7 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path,
|
|||
|
||||
bool Exists(const fs::path& path) {
|
||||
std::error_code ec;
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
if (Android::IsContentUri(path)) {
|
||||
return Android::Exists(path);
|
||||
} else {
|
||||
|
|
@ -545,7 +545,7 @@ bool Exists(const fs::path& path) {
|
|||
|
||||
bool IsFile(const fs::path& path) {
|
||||
std::error_code ec;
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
if (Android::IsContentUri(path)) {
|
||||
return !Android::IsDirectory(path);
|
||||
} else {
|
||||
|
|
@ -558,7 +558,7 @@ bool IsFile(const fs::path& path) {
|
|||
|
||||
bool IsDir(const fs::path& path) {
|
||||
std::error_code ec;
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
if (Android::IsContentUri(path)) {
|
||||
return Android::IsDirectory(path);
|
||||
} else {
|
||||
|
|
@ -611,7 +611,7 @@ fs::file_type GetEntryType(const fs::path& path) {
|
|||
}
|
||||
|
||||
u64 GetSize(const fs::path& path) {
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
if (Android::IsContentUri(path)) {
|
||||
return Android::GetSize(path);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
#include "common/assert.h"
|
||||
#include "common/fs/fs.h"
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
#include "common/fs/fs_android.h"
|
||||
#endif
|
||||
#include "common/fs/fs_paths.h"
|
||||
|
|
@ -126,7 +126,7 @@ public:
|
|||
LEGACY_PATH(Yuzu, YUZU)
|
||||
LEGACY_PATH(Suyu, SUYU)
|
||||
#undef LEGACY_PATH
|
||||
#elif ANDROID
|
||||
#elif __ANDROID__
|
||||
ASSERT(!eden_path.empty());
|
||||
eden_path_cache = eden_path / CACHE_DIR;
|
||||
eden_path_config = eden_path / CONFIG_DIR;
|
||||
|
|
@ -447,11 +447,11 @@ std::vector<std::string> SplitPathComponentsCopy(std::string_view filename) {
|
|||
|
||||
std::string SanitizePath(std::string_view path_, DirectorySeparator directory_separator) {
|
||||
std::string path(path_);
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
if (Android::IsContentUri(path)) {
|
||||
return path;
|
||||
}
|
||||
#endif // ANDROID
|
||||
#endif // __ANDROID__
|
||||
|
||||
char type1 = directory_separator == DirectorySeparator::BackwardSlash ? '/' : '\\';
|
||||
char type2 = directory_separator == DirectorySeparator::BackwardSlash ? '\\' : '/';
|
||||
|
|
@ -482,7 +482,7 @@ std::string GetParentPath(std::string_view path) {
|
|||
return std::string(path);
|
||||
}
|
||||
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
if (path[0] != '/') {
|
||||
std::string path_string{path};
|
||||
return FS::Android::GetParentDirectory(path_string);
|
||||
|
|
|
|||
|
|
@ -320,7 +320,7 @@ struct DebuggerBackend final : public Backend {
|
|||
void Flush() noexcept override {}
|
||||
};
|
||||
#endif
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
/// @brief Backend that writes to the Android logcat
|
||||
struct LogcatBackend : public Backend {
|
||||
explicit LogcatBackend() noexcept = default;
|
||||
|
|
@ -359,7 +359,7 @@ struct Impl {
|
|||
#ifdef _WIN32
|
||||
lambda(static_cast<Backend&>(debugger_backend));
|
||||
#endif
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
lambda(static_cast<Backend&>(lc_backend));
|
||||
#endif
|
||||
}
|
||||
|
|
@ -372,7 +372,7 @@ struct Impl {
|
|||
#ifdef _WIN32
|
||||
DebuggerBackend debugger_backend{};
|
||||
#endif
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
LogcatBackend lc_backend{};
|
||||
#endif
|
||||
std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
|
||||
|
|
|
|||
|
|
@ -359,7 +359,7 @@ struct Values {
|
|||
true,
|
||||
true};
|
||||
SwitchableSetting<int, true> fsr_sharpening_slider{linkage,
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
0,
|
||||
#else
|
||||
25,
|
||||
|
|
@ -417,7 +417,7 @@ struct Values {
|
|||
linkage, 0, "bg_blue", Category::Renderer, Specialization::Default, true, true};
|
||||
|
||||
SwitchableSetting<GpuAccuracy, true> gpu_accuracy{linkage,
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
GpuAccuracy::Low,
|
||||
#else
|
||||
GpuAccuracy::Medium,
|
||||
|
|
@ -447,7 +447,7 @@ struct Values {
|
|||
"nvdec_emulation", Category::RendererAdvanced};
|
||||
|
||||
SwitchableSetting<AnisotropyMode, true> max_anisotropy{linkage,
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
AnisotropyMode::Default,
|
||||
#else
|
||||
AnisotropyMode::Automatic,
|
||||
|
|
@ -500,7 +500,7 @@ struct Values {
|
|||
Category::RendererAdvanced};
|
||||
|
||||
SwitchableSetting<bool> use_reactive_flushing{linkage,
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
false,
|
||||
#else
|
||||
true,
|
||||
|
|
@ -519,7 +519,7 @@ struct Values {
|
|||
true,
|
||||
true};
|
||||
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
SwitchableSetting<bool> use_optimized_vertex_buffers{linkage,
|
||||
false,
|
||||
"use_optimized_vertex_buffers",
|
||||
|
|
@ -553,7 +553,7 @@ struct Values {
|
|||
true,
|
||||
true};
|
||||
SwitchableSetting<bool> async_presentation{linkage,
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
false,
|
||||
#else
|
||||
false,
|
||||
|
|
@ -599,7 +599,7 @@ struct Values {
|
|||
Category::RendererHacks};
|
||||
|
||||
SwitchableSetting<ExtendedDynamicState> dyna_state{linkage,
|
||||
#if defined(ANDROID)
|
||||
#if defined(__ANDROID__)
|
||||
ExtendedDynamicState::Disabled,
|
||||
#elif defined(__APPLE__)
|
||||
ExtendedDynamicState::Disabled,
|
||||
|
|
@ -618,7 +618,7 @@ struct Values {
|
|||
Specialization::Scalar};
|
||||
|
||||
SwitchableSetting<bool> vertex_input_dynamic_state{linkage,
|
||||
#if defined (ANDROID)
|
||||
#ifdef __ANDROID__
|
||||
false,
|
||||
#else
|
||||
true,
|
||||
|
|
@ -634,7 +634,7 @@ struct Values {
|
|||
linkage, false, "disable_shader_loop_safety_checks", Category::RendererDebug};
|
||||
Setting<bool> enable_renderdoc_hotkey{linkage, false, "renderdoc_hotkey",
|
||||
Category::RendererDebug};
|
||||
#if defined(ANDROID) && defined(ARCHITECTURE_arm64)
|
||||
#if defined(__ANDROID__) && defined(ARCHITECTURE_arm64)
|
||||
// Debug override for automatic BCn patching detection
|
||||
Setting<bool> patch_old_qcom_drivers{linkage, false, "patch_old_qcom_drivers",
|
||||
Category::RendererDebug};
|
||||
|
|
@ -679,7 +679,7 @@ struct Values {
|
|||
Setting<s32> current_user{linkage, 0, "current_user", Category::System};
|
||||
|
||||
SwitchableSetting<ConsoleMode> use_docked_mode{linkage,
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
ConsoleMode::Handheld,
|
||||
#else
|
||||
ConsoleMode::Docked,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2013 Dolphin Emulator Project
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
#include <common/fs/fs_android.h>
|
||||
#endif
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _
|
|||
if (full_path.empty())
|
||||
return false;
|
||||
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
if (full_path[0] != '/') {
|
||||
*_pPath = Common::FS::Android::GetParentDirectory(full_path);
|
||||
*_pFilename = Common::FS::Android::GetFilename(full_path);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2012 PPSSPP Project
|
||||
|
|
@ -10,12 +10,10 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#include <cstdlib>
|
||||
#endif
|
||||
#include <bit>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
#include <bit>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
#include <boost/asio.hpp>
|
||||
#include <boost/version.hpp>
|
||||
|
||||
#if BOOST_VERSION > 108400 && (!defined(_WINDOWS) && !defined(ANDROID)) || defined(YUZU_BOOST_v1)
|
||||
#if BOOST_VERSION > 108400 && (!defined(_WINDOWS) && !defined(__ANDROID__)) || defined(YUZU_BOOST_v1)
|
||||
#define USE_BOOST_v1
|
||||
#endif
|
||||
|
||||
|
|
@ -247,17 +247,19 @@ private:
|
|||
case DebuggerAction::Continue:
|
||||
MarkResumed([&] { ResumeEmulation(); });
|
||||
break;
|
||||
case DebuggerAction::StepThreadUnlocked:
|
||||
MarkResumed([&] {
|
||||
state->active_thread->SetStepState(Kernel::StepState::StepPending);
|
||||
state->active_thread->Resume(Kernel::SuspendType::Debug);
|
||||
ResumeEmulation(state->active_thread.GetPointerUnsafe());
|
||||
case DebuggerAction::ContinueThreads: {
|
||||
auto* gdb = static_cast<GDBStub*>(frontend.get());
|
||||
MarkResumed([this, threads = std::move(gdb->resume_threads)] {
|
||||
ResumeThreads(threads);
|
||||
});
|
||||
break;
|
||||
case DebuggerAction::StepThreadLocked: {
|
||||
MarkResumed([&] {
|
||||
}
|
||||
case DebuggerAction::StepThread: {
|
||||
auto* gdb = static_cast<GDBStub*>(frontend.get());
|
||||
MarkResumed([this, threads = std::move(gdb->resume_threads)] {
|
||||
state->active_thread->SetStepState(Kernel::StepState::StepPending);
|
||||
state->active_thread->Resume(Kernel::SuspendType::Debug);
|
||||
ResumeThreads(threads, state->active_thread.GetPointerUnsafe());
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
|
@ -298,6 +300,22 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
void ResumeThreads(const std::vector<Kernel::KThread*>& threads,
|
||||
Kernel::KThread* except = nullptr) {
|
||||
Kernel::KScopedLightLock ll{debug_process->GetListLock()};
|
||||
Kernel::KScopedSchedulerLock sl{system.Kernel()};
|
||||
|
||||
// Wake up only the specified threads.
|
||||
for (auto* thread : threads) {
|
||||
if (!thread || thread == except) {
|
||||
continue;
|
||||
}
|
||||
|
||||
thread->SetStepState(Kernel::StepState::NotStepping);
|
||||
thread->Resume(Kernel::SuspendType::Debug);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Callback>
|
||||
void MarkResumed(Callback&& cb) {
|
||||
stopped = false;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
|
@ -20,11 +20,11 @@ struct DebugWatchpoint;
|
|||
namespace Core {
|
||||
|
||||
enum class DebuggerAction {
|
||||
Interrupt, ///< Stop emulation as soon as possible.
|
||||
Continue, ///< Resume emulation.
|
||||
StepThreadLocked, ///< Step the currently-active thread without resuming others.
|
||||
StepThreadUnlocked, ///< Step the currently-active thread and resume others.
|
||||
ShutdownEmulation, ///< Shut down the emulator.
|
||||
Interrupt, ///< Stop emulation as soon as possible.
|
||||
Continue, ///< Resume emulation.
|
||||
ContinueThreads, ///< Resume only specific threads (listed in frontend).
|
||||
StepThread, ///< Step the active thread and resume only threads listed in frontend.
|
||||
ShutdownEmulation, ///< Shut down the emulator.
|
||||
};
|
||||
|
||||
class DebuggerBackend {
|
||||
|
|
|
|||
|
|
@ -4,15 +4,15 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cctype>
|
||||
#include <codecvt>
|
||||
#include <locale>
|
||||
#include <numeric>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/scope_exit.h"
|
||||
|
|
@ -273,10 +273,12 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction
|
|||
break;
|
||||
}
|
||||
case 's':
|
||||
actions.push_back(DebuggerAction::StepThreadLocked);
|
||||
resume_threads.clear();
|
||||
actions.push_back(DebuggerAction::StepThread);
|
||||
break;
|
||||
case 'C':
|
||||
case 'c':
|
||||
resume_threads.clear();
|
||||
actions.push_back(DebuggerAction::Continue);
|
||||
break;
|
||||
case 'Z':
|
||||
|
|
@ -467,29 +469,142 @@ void GDBStub::HandleQuery(std::string_view sv) {
|
|||
}
|
||||
|
||||
void GDBStub::HandleVCont(std::string_view sv, std::vector<DebuggerAction>& actions) {
|
||||
// Continuing and stepping are supported (signal is ignored, but required for GDB to use vCont)
|
||||
// Continuing and stepping are supported (signal is ignored, but required for GDB to use vCont).
|
||||
// Reference: https://sourceware.org/gdb/current/onlinedocs/gdb.html/Packets.html#vCont-packet
|
||||
if (sv == "?") {
|
||||
SendReply("vCont;c;C;s;S");
|
||||
} else {
|
||||
Kernel::KThread* stepped_thread = nullptr;
|
||||
bool lock_execution = true;
|
||||
std::vector<std::string> entries;
|
||||
boost::split(entries, sv.substr(1), boost::is_any_of(";"));
|
||||
for (auto const& thread_action : entries) {
|
||||
std::vector<std::string> parts;
|
||||
boost::split(parts, thread_action, boost::is_any_of(":"));
|
||||
if (parts.size() == 1 && (parts[0] == "c" || parts[0].starts_with("C")))
|
||||
lock_execution = false;
|
||||
if (parts.size() == 2 && (parts[0] == "s" || parts[0].starts_with("S")))
|
||||
stepped_thread = GetThreadByID(strtoll(parts[1].data(), nullptr, 16));
|
||||
return;
|
||||
}
|
||||
if (sv.empty() || sv.front() != ';') {
|
||||
SendReply(GDB_STUB_REPLY_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
enum class VContAction {
|
||||
Continue,
|
||||
Step,
|
||||
};
|
||||
struct VContDirective {
|
||||
VContAction action;
|
||||
Kernel::KThread* thread{};
|
||||
bool all_threads{};
|
||||
|
||||
bool Matches(Kernel::KThread* candidate) const {
|
||||
return all_threads || thread == candidate;
|
||||
}
|
||||
};
|
||||
|
||||
const auto is_hex_byte = [](std::string_view value) {
|
||||
return value.size() == 2 && std::isxdigit(static_cast<unsigned char>(value[0])) &&
|
||||
std::isxdigit(static_cast<unsigned char>(value[1]));
|
||||
};
|
||||
const auto is_hex_string = [](std::string_view value) {
|
||||
return std::ranges::all_of(value, [](auto const c) { return std::isxdigit(int(c)); });
|
||||
};
|
||||
|
||||
resume_threads.clear();
|
||||
|
||||
std::vector<VContDirective> directives;
|
||||
std::string_view remaining = sv.substr(1);
|
||||
while (!remaining.empty()) {
|
||||
const auto entry_end = remaining.find(';');
|
||||
const auto entry = remaining.substr(0, entry_end);
|
||||
remaining = entry_end == std::string_view::npos ? std::string_view{} : remaining.substr(entry_end + 1);
|
||||
|
||||
if (entry.empty()) {
|
||||
SendReply(GDB_STUB_REPLY_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (stepped_thread) {
|
||||
backend.SetActiveThread(stepped_thread);
|
||||
actions.push_back(lock_execution ? DebuggerAction::StepThreadLocked : DebuggerAction::StepThreadUnlocked);
|
||||
} else {
|
||||
actions.push_back(DebuggerAction::Continue);
|
||||
const auto thread_sep = entry.find(':');
|
||||
const auto action_token = entry.substr(0, thread_sep);
|
||||
const auto thread_token = thread_sep == std::string_view::npos ? std::string_view{} : entry.substr(thread_sep + 1);
|
||||
|
||||
if (action_token.empty()) {
|
||||
SendReply(GDB_STUB_REPLY_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
VContDirective directive;
|
||||
if (action_token == "c") {
|
||||
directive.action = VContAction::Continue;
|
||||
} else if (action_token.front() == 'C' && is_hex_byte(action_token.substr(1))) {
|
||||
directive.action = VContAction::Continue;
|
||||
} else if (action_token == "s") {
|
||||
directive.action = VContAction::Step;
|
||||
} else if (action_token.front() == 'S' && is_hex_byte(action_token.substr(1))) {
|
||||
directive.action = VContAction::Step;
|
||||
} else {
|
||||
SendReply(GDB_STUB_REPLY_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (thread_sep == std::string_view::npos || thread_token == "-1") {
|
||||
directive.all_threads = true;
|
||||
} else if (thread_token == "0") {
|
||||
// A thread-id of 0 selects an arbitrary thread. While stopped, use the
|
||||
// current active thread as that arbitrary choice.
|
||||
directive.thread = backend.GetActiveThread();
|
||||
} else if (thread_token.starts_with('p')) {
|
||||
// We do not currently support multiprocess thread selectors.
|
||||
SendReply(GDB_STUB_REPLY_ERR);
|
||||
return;
|
||||
} else if (is_hex_string(thread_token)) {
|
||||
directive.thread = GetThreadByID(strtoull(std::string(thread_token).c_str(), nullptr, 16));
|
||||
} else {
|
||||
SendReply(GDB_STUB_REPLY_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
directives.push_back(directive);
|
||||
}
|
||||
|
||||
if (directives.empty()) {
|
||||
SendReply(GDB_STUB_REPLY_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
// Resolve the packet exactly as specified by the protocol: for each thread,
|
||||
// the leftmost action with a matching thread-id wins.
|
||||
Kernel::KThread* stepped_thread = nullptr;
|
||||
std::vector<Kernel::KThread*> continue_threads;
|
||||
auto& thread_list = debug_process->GetThreadList();
|
||||
for (auto& thread : thread_list) {
|
||||
const auto directive = std::find_if(directives.begin(), directives.end(),
|
||||
[&](const VContDirective& candidate) {
|
||||
return candidate.Matches(std::addressof(thread));
|
||||
});
|
||||
if (directive == directives.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (directive->action) {
|
||||
case VContAction::Continue:
|
||||
continue_threads.push_back(std::addressof(thread));
|
||||
break;
|
||||
case VContAction::Step:
|
||||
if (stepped_thread) {
|
||||
// The core can step at most one thread at a time.
|
||||
SendReply(GDB_STUB_REPLY_ERR);
|
||||
return;
|
||||
}
|
||||
stepped_thread = std::addressof(thread);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (stepped_thread) {
|
||||
backend.SetActiveThread(stepped_thread);
|
||||
resume_threads = std::move(continue_threads);
|
||||
actions.push_back(DebuggerAction::StepThread);
|
||||
} else if (continue_threads.size() == thread_list.size()) {
|
||||
actions.push_back(DebuggerAction::Continue);
|
||||
} else if (!continue_threads.empty()) {
|
||||
resume_threads = std::move(continue_threads);
|
||||
actions.push_back(DebuggerAction::ContinueThreads);
|
||||
} else {
|
||||
// A resume packet that leaves all threads stopped is not useful to execute.
|
||||
SendReply(GDB_STUB_REPLY_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
|
|
@ -52,6 +52,7 @@ struct GDBStub : public DebuggerFrontend {
|
|||
std::unique_ptr<GDBStubArch> arch;
|
||||
std::vector<char> current_command;
|
||||
std::map<VAddr, u32> replaced_instructions;
|
||||
std::vector<Kernel::KThread*> resume_threads;
|
||||
bool no_ack{};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
#define stat _stat64
|
||||
#endif
|
||||
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
#include "common/fs/fs_android.h"
|
||||
#endif
|
||||
|
||||
|
|
@ -288,7 +288,7 @@ RealVfsFile::~RealVfsFile() {
|
|||
}
|
||||
|
||||
std::string RealVfsFile::GetName() const {
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
if (path[0] != '/') {
|
||||
return FS::Android::GetFilename(path);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ RenderdocAPI::RenderdocAPI() {
|
|||
#elif defined(__HAIKU__)
|
||||
// no rtld on haiku
|
||||
#else
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
static constexpr const char RENDERDOC_LIB[] = "libVkLayer_GLES_RenderDoc.so";
|
||||
#else
|
||||
static constexpr const char RENDERDOC_LIB[] = "librenderdoc.so";
|
||||
|
|
|
|||
|
|
@ -299,10 +299,24 @@ if ("loongarch64" IN_LIST ARCHITECTURE)
|
|||
target_link_libraries(dynarmic PRIVATE lagoon::lagoon)
|
||||
|
||||
target_sources(dynarmic PRIVATE
|
||||
backend/loongarch64/exclusive_monitor.cpp
|
||||
backend/loongarch64/abi.h
|
||||
backend/loongarch64/a32_jitstate.cpp
|
||||
backend/loongarch64/a32_jitstate.h
|
||||
backend/loongarch64/code_block.h
|
||||
backend/loongarch64/emit_context.h
|
||||
backend/loongarch64/emit_loongarch64.cpp
|
||||
backend/loongarch64/emit_loongarch64.h
|
||||
backend/loongarch64/emit_loongarch64_a32.cpp
|
||||
backend/loongarch64/emit_loongarch64_data_processing.cpp
|
||||
backend/loongarch64/a32_address_space.cpp
|
||||
backend/loongarch64/a32_address_space.h
|
||||
backend/loongarch64/a32_interface.cpp
|
||||
backend/loongarch64/a64_interface.cpp
|
||||
backend/loongarch64/code_block.h
|
||||
backend/loongarch64/exclusive_monitor.cpp
|
||||
backend/loongarch64/reg_alloc.cpp
|
||||
backend/loongarch64/reg_alloc.h
|
||||
backend/loongarch64/stack_layout.h
|
||||
backend/loongarch64/lagoon_cpp.h
|
||||
|
||||
common/spin_lock_loongarch64.cpp
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,123 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "dynarmic/backend/loongarch64/a32_address_space.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
|
||||
#include "dynarmic/backend/loongarch64/a32_jitstate.h"
|
||||
#include "dynarmic/backend/loongarch64/abi.h"
|
||||
#include "dynarmic/backend/loongarch64/emit_loongarch64.h"
|
||||
#include "dynarmic/frontend/A32/a32_location_descriptor.h"
|
||||
#include "dynarmic/frontend/A32/translate/a32_translate.h"
|
||||
#include "dynarmic/ir/opt_passes.h"
|
||||
|
||||
namespace Dynarmic::Backend::LoongArch64 {
|
||||
|
||||
A32AddressSpace::A32AddressSpace(const A32::UserConfig& conf)
|
||||
: conf(conf)
|
||||
, cb(conf.code_cache_size) {
|
||||
EmitPrelude();
|
||||
}
|
||||
|
||||
void A32AddressSpace::GenerateIR(IR::Block& ir_block, IR::LocationDescriptor descriptor) const {
|
||||
A32::Translate(ir_block, A32::LocationDescriptor{descriptor}, conf.callbacks, {conf.arch_version, conf.define_unpredictable_behaviour, conf.hook_hint_instructions});
|
||||
Optimization::Optimize(ir_block, conf, {.sha256 = true});
|
||||
}
|
||||
|
||||
CodePtr A32AddressSpace::Get(IR::LocationDescriptor descriptor) {
|
||||
if (const auto iter = block_entries.find(descriptor.Value()); iter != block_entries.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CodePtr A32AddressSpace::GetOrEmit(IR::LocationDescriptor descriptor) {
|
||||
if (CodePtr block_entry = Get(descriptor)) {
|
||||
return block_entry;
|
||||
}
|
||||
|
||||
IR::Block ir_block{descriptor};
|
||||
GenerateIR(ir_block, descriptor);
|
||||
const EmittedBlockInfo block_info = Emit(std::move(ir_block));
|
||||
|
||||
block_infos.insert_or_assign(descriptor.Value(), block_info);
|
||||
block_entries.insert_or_assign(descriptor.Value(), block_info.entry_point);
|
||||
return block_info.entry_point;
|
||||
}
|
||||
|
||||
void A32AddressSpace::ClearCache() {
|
||||
block_entries.clear();
|
||||
block_infos.clear();
|
||||
SetCursorPtr(prelude_info.end_of_prelude);
|
||||
}
|
||||
|
||||
void A32AddressSpace::EmitPrelude() {
|
||||
prelude_info.run_code = GetCursorPtr<PreludeInfo::RunCodeFuncType>();
|
||||
|
||||
// Save all GPRs except sp (r3) and tp (r2)
|
||||
la_addi_d(&cb.as, LA_SP, LA_SP, -64 * 8);
|
||||
for (u32 i = 1; i < 32; i++) {
|
||||
if (i == LA_SP || i == LA_TP)
|
||||
continue;
|
||||
la_st_d(&cb.as, static_cast<la_gpr_t>(i), LA_SP, static_cast<int32_t>(i * 8));
|
||||
}
|
||||
|
||||
// Set up reserved registers and jump to block entry
|
||||
la_move(&cb.as, Xstate, LA_A1); // Xstate = state ptr
|
||||
la_move(&cb.as, Xhalt, LA_A2); // Xhalt = halt reason ptr
|
||||
la_jr(&cb.as, LA_A0); // jump to block_entry
|
||||
|
||||
prelude_info.return_from_run_code = GetCursorPtr<CodePtr>();
|
||||
|
||||
// Restore all GPRs except sp and tp
|
||||
for (u32 i = 1; i < 32; i++) {
|
||||
if (i == LA_SP || i == LA_TP)
|
||||
continue;
|
||||
la_ld_d(&cb.as, static_cast<la_gpr_t>(i), LA_SP, static_cast<int32_t>(i * 8));
|
||||
}
|
||||
la_addi_d(&cb.as, LA_SP, LA_SP, 64 * 8);
|
||||
la_ret(&cb.as);
|
||||
|
||||
prelude_info.end_of_prelude = GetCursorPtr<CodePtr>();
|
||||
}
|
||||
|
||||
void A32AddressSpace::SetCursorPtr(CodePtr ptr) {
|
||||
cb.as.cursor = reinterpret_cast<uint8_t*>(ptr);
|
||||
}
|
||||
|
||||
size_t A32AddressSpace::GetRemainingSize() {
|
||||
return la_get_remaining_buffer_size(&cb.as);
|
||||
}
|
||||
|
||||
EmittedBlockInfo A32AddressSpace::Emit(IR::Block block) {
|
||||
if (GetRemainingSize() < 1024 * 1024) {
|
||||
ClearCache();
|
||||
}
|
||||
|
||||
EmitConfig emit_conf{};
|
||||
EmittedBlockInfo block_info = EmitLoongArch64(cb.as, std::move(block), emit_conf);
|
||||
Link(block_info);
|
||||
|
||||
return block_info;
|
||||
}
|
||||
|
||||
void A32AddressSpace::Link(EmittedBlockInfo& block_info) {
|
||||
for (const auto& reloc : block_info.relocations) {
|
||||
uint8_t* patch_location = reinterpret_cast<uint8_t*>(block_info.entry_point) + reloc.code_offset;
|
||||
|
||||
switch (reloc.target) {
|
||||
case LinkTarget::ReturnFromRunCode: {
|
||||
std::ptrdiff_t off = reinterpret_cast<uint8_t*>(prelude_info.return_from_run_code) - patch_location;
|
||||
lagoon_assembler_t patch_as;
|
||||
la_init_assembler(&patch_as, patch_location, 4);
|
||||
la_b(&patch_as, static_cast<int32_t>(off));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ASSERT(false && "Invalid relocation target");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Dynarmic::Backend::LoongArch64
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "dynarmic/backend/loongarch64/lagoon_cpp.h"
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include "dynarmic/backend/loongarch64/code_block.h"
|
||||
#include "dynarmic/backend/loongarch64/emit_loongarch64.h"
|
||||
#include "dynarmic/interface/A32/config.h"
|
||||
#include "dynarmic/interface/halt_reason.h"
|
||||
#include "dynarmic/ir/basic_block.h"
|
||||
#include "dynarmic/ir/location_descriptor.h"
|
||||
|
||||
namespace Dynarmic::Backend::LoongArch64 {
|
||||
|
||||
struct A32JitState;
|
||||
|
||||
class A32AddressSpace final {
|
||||
public:
|
||||
explicit A32AddressSpace(const A32::UserConfig& conf);
|
||||
|
||||
void GenerateIR(IR::Block& ir_block, IR::LocationDescriptor descriptor) const;
|
||||
CodePtr Get(IR::LocationDescriptor descriptor);
|
||||
CodePtr GetOrEmit(IR::LocationDescriptor descriptor);
|
||||
|
||||
void ClearCache();
|
||||
|
||||
private:
|
||||
void EmitPrelude();
|
||||
|
||||
template<typename T>
|
||||
T GetCursorPtr() {
|
||||
return reinterpret_cast<T>(cb.as.cursor);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T GetCursorPtr() const {
|
||||
return reinterpret_cast<T>(cb.as.cursor);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T GetMemPtr() const {
|
||||
return cb.ptr<T>();
|
||||
}
|
||||
|
||||
void SetCursorPtr(CodePtr ptr);
|
||||
size_t GetRemainingSize();
|
||||
EmittedBlockInfo Emit(IR::Block block);
|
||||
void Link(EmittedBlockInfo& block_info);
|
||||
|
||||
const A32::UserConfig conf;
|
||||
CodeBlock cb;
|
||||
|
||||
ankerl::unordered_dense::map<u64, CodePtr> block_entries;
|
||||
ankerl::unordered_dense::map<u64, EmittedBlockInfo> block_infos;
|
||||
|
||||
public:
|
||||
struct PreludeInfo {
|
||||
CodePtr end_of_prelude;
|
||||
using RunCodeFuncType = HaltReason (*)(CodePtr entry_point, A32JitState* context, volatile u32* halt_reason);
|
||||
RunCodeFuncType run_code;
|
||||
CodePtr return_from_run_code;
|
||||
} prelude_info;
|
||||
};
|
||||
|
||||
} // namespace Dynarmic::Backend::LoongArch64
|
||||
|
|
@ -1,101 +1,134 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <mutex>
|
||||
|
||||
#include <boost/icl/interval_set.hpp>
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
#include "dynarmic/backend/loongarch64/a32_address_space.h"
|
||||
#include "dynarmic/backend/loongarch64/a32_jitstate.h"
|
||||
#include "dynarmic/common/atomic.h"
|
||||
#include "dynarmic/frontend/A32/a32_location_descriptor.h"
|
||||
#include "dynarmic/interface/A32/a32.h"
|
||||
|
||||
namespace Dynarmic::A32 {
|
||||
|
||||
using namespace Backend::LoongArch64;
|
||||
|
||||
struct Jit::Impl final {
|
||||
explicit Impl(UserConfig conf_) : conf(std::move(conf_)) {}
|
||||
Impl(Jit* jit_interface, A32::UserConfig conf)
|
||||
: jit_interface(jit_interface)
|
||||
, conf(conf)
|
||||
, current_address_space(conf) {}
|
||||
|
||||
HaltReason Run() {
|
||||
UNIMPLEMENTED();
|
||||
return halt_reason;
|
||||
ASSERT(!jit_interface->is_executing);
|
||||
jit_interface->is_executing = true;
|
||||
|
||||
const auto location_descriptor = current_state.GetLocationDescriptor();
|
||||
const auto entry_point = current_address_space.GetOrEmit(location_descriptor);
|
||||
current_address_space.prelude_info.run_code(entry_point, ¤t_state, &halt_reason);
|
||||
|
||||
HaltReason hr = static_cast<HaltReason>(Atomic::Exchange(&halt_reason, 0));
|
||||
jit_interface->is_executing = false;
|
||||
return hr;
|
||||
}
|
||||
|
||||
HaltReason Step() {
|
||||
UNIMPLEMENTED();
|
||||
return halt_reason | HaltReason::Step;
|
||||
ASSERT(!jit_interface->is_executing);
|
||||
jit_interface->is_executing = true;
|
||||
|
||||
const auto location_descriptor = A32::LocationDescriptor{current_state.GetLocationDescriptor()}.SetSingleStepping(true);
|
||||
const auto entry_point = current_address_space.GetOrEmit(location_descriptor);
|
||||
current_address_space.prelude_info.run_code(entry_point, ¤t_state, &halt_reason);
|
||||
|
||||
HaltReason hr = static_cast<HaltReason>(Atomic::Exchange(&halt_reason, 0));
|
||||
jit_interface->is_executing = false;
|
||||
return hr;
|
||||
}
|
||||
|
||||
void ClearCache() {
|
||||
std::unique_lock lock{invalidation_mutex};
|
||||
invalidate_entire_cache = true;
|
||||
HaltExecution(HaltReason::CacheInvalidation);
|
||||
}
|
||||
|
||||
void InvalidateCacheRange(u32, std::size_t) {
|
||||
void InvalidateCacheRange(u32 start_address, size_t length) {
|
||||
std::unique_lock lock{invalidation_mutex};
|
||||
invalid_cache_ranges.add(boost::icl::discrete_interval<u32>::closed(start_address, static_cast<u32>(start_address + length - 1)));
|
||||
HaltExecution(HaltReason::CacheInvalidation);
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
regs = {};
|
||||
ext_regs = {};
|
||||
cpsr = 0;
|
||||
fpscr = 0;
|
||||
halt_reason = {};
|
||||
current_state = {};
|
||||
}
|
||||
|
||||
void HaltExecution(HaltReason hr) {
|
||||
halt_reason |= hr;
|
||||
Atomic::Or(&halt_reason, static_cast<u32>(hr));
|
||||
}
|
||||
|
||||
void ClearHalt(HaltReason hr) {
|
||||
halt_reason &= ~hr;
|
||||
Atomic::And(&halt_reason, ~static_cast<u32>(hr));
|
||||
}
|
||||
|
||||
std::array<u32, 16>& Regs() {
|
||||
return regs;
|
||||
return current_state.regs;
|
||||
}
|
||||
|
||||
const std::array<u32, 16>& Regs() const {
|
||||
return regs;
|
||||
return current_state.regs;
|
||||
}
|
||||
|
||||
std::array<u32, 64>& ExtRegs() {
|
||||
return ext_regs;
|
||||
return current_state.ext_regs;
|
||||
}
|
||||
|
||||
const std::array<u32, 64>& ExtRegs() const {
|
||||
return ext_regs;
|
||||
return current_state.ext_regs;
|
||||
}
|
||||
|
||||
u32 Cpsr() const {
|
||||
return cpsr;
|
||||
return current_state.Cpsr();
|
||||
}
|
||||
|
||||
void SetCpsr(u32 value) {
|
||||
cpsr = value;
|
||||
current_state.SetCpsr(value);
|
||||
}
|
||||
|
||||
u32 Fpscr() const {
|
||||
return fpscr;
|
||||
return current_state.Fpscr();
|
||||
}
|
||||
|
||||
void SetFpscr(u32 value) {
|
||||
fpscr = value;
|
||||
current_state.SetFpscr(value);
|
||||
}
|
||||
|
||||
void ClearExclusiveState() {}
|
||||
void ClearExclusiveState() {
|
||||
current_state.exclusive_state = false;
|
||||
}
|
||||
|
||||
std::string Disassemble() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
UserConfig conf;
|
||||
std::array<u32, 16> regs{};
|
||||
std::array<u32, 64> ext_regs{};
|
||||
u32 cpsr = 0;
|
||||
u32 fpscr = 0;
|
||||
HaltReason halt_reason{};
|
||||
private:
|
||||
Jit* jit_interface;
|
||||
A32::UserConfig conf;
|
||||
A32JitState current_state{};
|
||||
A32AddressSpace current_address_space;
|
||||
|
||||
volatile u32 halt_reason = 0;
|
||||
|
||||
std::mutex invalidation_mutex;
|
||||
boost::icl::interval_set<u32> invalid_cache_ranges;
|
||||
bool invalidate_entire_cache = false;
|
||||
};
|
||||
|
||||
Jit::Jit(UserConfig conf) : impl(std::make_unique<Impl>(std::move(conf))) {}
|
||||
Jit::Jit(UserConfig conf)
|
||||
: impl(std::make_unique<Impl>(this, conf)) {}
|
||||
|
||||
Jit::~Jit() = default;
|
||||
|
||||
|
|
@ -111,7 +144,7 @@ void Jit::ClearCache() {
|
|||
impl->ClearCache();
|
||||
}
|
||||
|
||||
void Jit::InvalidateCacheRange(u32 start_address, std::size_t length) {
|
||||
void Jit::InvalidateCacheRange(u32 start_address, size_t length) {
|
||||
impl->InvalidateCacheRange(start_address, length);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "dynarmic/backend/loongarch64/a32_jitstate.h"
|
||||
|
||||
#include "dynarmic/mcl/bit.hpp"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Dynarmic::Backend::LoongArch64 {
|
||||
|
||||
u32 A32JitState::Cpsr() const {
|
||||
u32 cpsr = 0;
|
||||
cpsr |= cpsr_nzcv;
|
||||
cpsr |= cpsr_q;
|
||||
cpsr |= mcl::bit::get_bit<31>(cpsr_ge) ? 1 << 19 : 0;
|
||||
cpsr |= mcl::bit::get_bit<23>(cpsr_ge) ? 1 << 18 : 0;
|
||||
cpsr |= mcl::bit::get_bit<15>(cpsr_ge) ? 1 << 17 : 0;
|
||||
cpsr |= mcl::bit::get_bit<7>(cpsr_ge) ? 1 << 16 : 0;
|
||||
cpsr |= mcl::bit::get_bit<1>(upper_location_descriptor) ? 1 << 9 : 0;
|
||||
cpsr |= mcl::bit::get_bit<0>(upper_location_descriptor) ? 1 << 5 : 0;
|
||||
cpsr |= static_cast<u32>(upper_location_descriptor & 0b11111100'00000000);
|
||||
cpsr |= static_cast<u32>(upper_location_descriptor & 0b00000011'00000000) << 17;
|
||||
cpsr |= cpsr_jaifm;
|
||||
return cpsr;
|
||||
}
|
||||
|
||||
void A32JitState::SetCpsr(u32 cpsr) {
|
||||
cpsr_nzcv = cpsr & 0xF0000000;
|
||||
cpsr_q = cpsr & (1 << 27);
|
||||
cpsr_ge = 0;
|
||||
cpsr_ge |= mcl::bit::get_bit<19>(cpsr) ? 0xFF000000 : 0;
|
||||
cpsr_ge |= mcl::bit::get_bit<18>(cpsr) ? 0x00FF0000 : 0;
|
||||
cpsr_ge |= mcl::bit::get_bit<17>(cpsr) ? 0x0000FF00 : 0;
|
||||
cpsr_ge |= mcl::bit::get_bit<16>(cpsr) ? 0x000000FF : 0;
|
||||
|
||||
upper_location_descriptor &= 0xFFFF0000;
|
||||
upper_location_descriptor |= mcl::bit::get_bit<9>(cpsr) ? 2 : 0;
|
||||
upper_location_descriptor |= mcl::bit::get_bit<5>(cpsr) ? 1 : 0;
|
||||
upper_location_descriptor |= (cpsr >> 0) & 0b11111100'00000000;
|
||||
upper_location_descriptor |= (cpsr >> 17) & 0b00000011'00000000;
|
||||
cpsr_jaifm = cpsr & 0x010001DF;
|
||||
}
|
||||
|
||||
constexpr u32 FPCR_MASK = A32::LocationDescriptor::FPSCR_MODE_MASK;
|
||||
constexpr u32 FPSR_MASK = 0x0800009F;
|
||||
|
||||
u32 A32JitState::Fpscr() const {
|
||||
return (upper_location_descriptor & FPCR_MASK) | fpsr | fpsr_nzcv;
|
||||
}
|
||||
|
||||
void A32JitState::SetFpscr(u32 fpscr) {
|
||||
fpsr = fpscr & FPSR_MASK;
|
||||
fpsr_nzcv = fpscr & 0xF0000000;
|
||||
upper_location_descriptor = (upper_location_descriptor & 0x0000ffff) | (fpscr & FPCR_MASK);
|
||||
}
|
||||
|
||||
} // namespace Dynarmic::Backend::LoongArch64
|
||||
43
src/dynarmic/src/dynarmic/backend/loongarch64/a32_jitstate.h
Normal file
43
src/dynarmic/src/dynarmic/backend/loongarch64/a32_jitstate.h
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
#include "dynarmic/frontend/A32/a32_location_descriptor.h"
|
||||
#include "dynarmic/ir/location_descriptor.h"
|
||||
|
||||
namespace Dynarmic::Backend::LoongArch64 {
|
||||
|
||||
struct A32JitState {
|
||||
u32 cpsr_nzcv = 0;
|
||||
u32 cpsr_q = 0;
|
||||
u32 cpsr_jaifm = 0;
|
||||
u32 cpsr_ge = 0;
|
||||
|
||||
u32 fpsr = 0;
|
||||
u32 fpsr_nzcv = 0;
|
||||
|
||||
std::array<u32, 16> regs{};
|
||||
|
||||
u32 upper_location_descriptor;
|
||||
|
||||
alignas(16) std::array<u32, 64> ext_regs{};
|
||||
|
||||
u32 exclusive_state = 0;
|
||||
|
||||
u32 Cpsr() const;
|
||||
void SetCpsr(u32 cpsr);
|
||||
|
||||
u32 Fpscr() const;
|
||||
void SetFpscr(u32 fpscr);
|
||||
|
||||
IR::LocationDescriptor GetLocationDescriptor() const {
|
||||
return IR::LocationDescriptor{regs[15] | (static_cast<u64>(upper_location_descriptor) << 32)};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Dynarmic::Backend::LoongArch64
|
||||
39
src/dynarmic/src/dynarmic/backend/loongarch64/abi.h
Normal file
39
src/dynarmic/src/dynarmic/backend/loongarch64/abi.h
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <initializer_list>
|
||||
|
||||
#include "dynarmic/backend/loongarch64/lagoon_cpp.h"
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Dynarmic::Backend::LoongArch64 {
|
||||
|
||||
// Reserved registers used by the dynarmic JIT
|
||||
// Xstate (s0/r23): pointer to JIT CPU state, callee-saved
|
||||
// Xhalt (s1/r24): halt reason pointer, callee-saved
|
||||
// Xscratch0 (t7/r19): scratch register (caller-saved)
|
||||
// Xscratch1 (t8/r20): scratch register (caller-saved)
|
||||
constexpr la_gpr_t Xstate = LA_S0; // r23
|
||||
constexpr la_gpr_t Xhalt = LA_S1; // r24
|
||||
constexpr la_gpr_t Xscratch0 = LA_T7; // r19
|
||||
constexpr la_gpr_t Xscratch1 = LA_T8; // r20
|
||||
|
||||
// GPR allocation order: callee-saved (s2-s8) first, then temps, then args
|
||||
constexpr std::initializer_list<u32> GPR_ORDER{
|
||||
25, 26, 27, 28, 29, 30, 31, // s2-s8 (r25-r31)
|
||||
12, 13, 14, 15, 16, 17, 18, // t0-t6 (r12-r18)
|
||||
4, 5, 6, 7, 8, 9, 10, 11 // a0-a7 (r4-r11)
|
||||
};
|
||||
|
||||
// FPR allocation order: callee-saved (fs0-fs7) first, then ft, then fa
|
||||
constexpr std::initializer_list<u32> FPR_ORDER{
|
||||
24, 25, 26, 27, 28, 29, 30, 31, // fs0-fs7 (f24-f31)
|
||||
8, 9, 10, 11, 12, 13, 14, 15, // ft0-ft7 (f8-f15)
|
||||
16, 17, 18, 19, 20, 21, 22, 23, // ft8-ft15 (f16-f23)
|
||||
0, 1, 2, 3, 4, 5, 6, 7 // fa0-fa7 (f0-f7)
|
||||
};
|
||||
|
||||
} // namespace Dynarmic::Backend::LoongArch64
|
||||
|
|
@ -4,20 +4,44 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
|
||||
#include <sys/mman.h>
|
||||
#include "dynarmic/backend/loongarch64/lagoon_cpp.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Dynarmic::Backend::LoongArch64 {
|
||||
|
||||
class CodeBlock {
|
||||
public:
|
||||
explicit CodeBlock(std::size_t size) noexcept
|
||||
: memsize(size) {
|
||||
mem = static_cast<u8*>(mmap(nullptr, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0));
|
||||
ASSERT(mem != MAP_FAILED);
|
||||
la_init_assembler(&as, mem, size);
|
||||
}
|
||||
|
||||
~CodeBlock() noexcept {
|
||||
if (mem == nullptr) {
|
||||
return;
|
||||
}
|
||||
munmap(mem, memsize);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T ptr() const noexcept {
|
||||
static_assert(std::is_pointer_v<T> || std::is_same_v<T, std::uintptr_t> || std::is_same_v<T, std::intptr_t>);
|
||||
return reinterpret_cast<T>(mem);
|
||||
}
|
||||
|
||||
lagoon_assembler_t as{};
|
||||
|
||||
private:
|
||||
u8* mem = nullptr;
|
||||
size_t memsize = 0;
|
||||
};
|
||||
|
||||
} // namespace Dynarmic::Backend::LoongArch64
|
||||
|
|
|
|||
24
src/dynarmic/src/dynarmic/backend/loongarch64/emit_context.h
Normal file
24
src/dynarmic/src/dynarmic/backend/loongarch64/emit_context.h
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "dynarmic/backend/loongarch64/emit_loongarch64.h"
|
||||
#include "dynarmic/backend/loongarch64/reg_alloc.h"
|
||||
|
||||
namespace Dynarmic::IR {
|
||||
class Block;
|
||||
} // namespace Dynarmic::IR
|
||||
|
||||
namespace Dynarmic::Backend::LoongArch64 {
|
||||
|
||||
struct EmitConfig;
|
||||
|
||||
struct EmitContext {
|
||||
IR::Block& block;
|
||||
RegAlloc& reg_alloc;
|
||||
const EmitConfig& emit_conf;
|
||||
EmittedBlockInfo& ebi;
|
||||
};
|
||||
|
||||
} // namespace Dynarmic::Backend::LoongArch64
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "dynarmic/backend/loongarch64/emit_loongarch64.h"
|
||||
|
||||
#include "dynarmic/backend/loongarch64/a32_jitstate.h"
|
||||
#include "dynarmic/backend/loongarch64/abi.h"
|
||||
#include "dynarmic/backend/loongarch64/emit_context.h"
|
||||
#include "dynarmic/backend/loongarch64/reg_alloc.h"
|
||||
#include "dynarmic/ir/basic_block.h"
|
||||
#include "dynarmic/ir/microinstruction.h"
|
||||
#include "dynarmic/ir/opcodes.h"
|
||||
|
||||
namespace Dynarmic::Backend::LoongArch64 {
|
||||
|
||||
template<IR::Opcode op>
|
||||
void EmitIR(lagoon_assembler_t&, EmitContext&, IR::Inst*) {
|
||||
ASSERT(false && "Unimplemented opcode");
|
||||
}
|
||||
|
||||
template<>
|
||||
void EmitIR<IR::Opcode::Void>(lagoon_assembler_t&, EmitContext&, IR::Inst*) {}
|
||||
|
||||
template<>
|
||||
void EmitIR<IR::Opcode::A32GetRegister>(lagoon_assembler_t&, EmitContext& ctx, IR::Inst* inst);
|
||||
|
||||
template<>
|
||||
void EmitIR<IR::Opcode::A32SetRegister>(lagoon_assembler_t&, EmitContext& ctx, IR::Inst* inst);
|
||||
|
||||
template<>
|
||||
void EmitIR<IR::Opcode::A32SetCpsrNZC>(lagoon_assembler_t&, EmitContext& ctx, IR::Inst* inst);
|
||||
|
||||
template<>
|
||||
void EmitIR<IR::Opcode::LogicalShiftLeft32>(lagoon_assembler_t&, EmitContext& ctx, IR::Inst* inst);
|
||||
|
||||
template<>
|
||||
void EmitIR<IR::Opcode::GetCarryFromOp>(lagoon_assembler_t&, EmitContext& ctx, IR::Inst* inst) {
|
||||
ASSERT(ctx.reg_alloc.IsValueLive(inst));
|
||||
}
|
||||
|
||||
template<>
|
||||
void EmitIR<IR::Opcode::GetNZFromOp>(lagoon_assembler_t& as, EmitContext& ctx, IR::Inst* inst) {
|
||||
auto args = ctx.reg_alloc.GetArgumentInfo(inst);
|
||||
|
||||
auto Xvalue = ctx.reg_alloc.ReadX(args[0]);
|
||||
auto Xnz = ctx.reg_alloc.WriteX(inst);
|
||||
RegAlloc::Realize(Xvalue, Xnz);
|
||||
|
||||
// Z flag (bit 30): set if value == 0
|
||||
la_sltui(&as, Xnz->index, Xvalue->index, 1);
|
||||
la_slli_d(&as, Xnz->index, Xnz->index, 30);
|
||||
// N flag (bit 31): set if value < 0 (signed)
|
||||
la_slt(&as, Xscratch0, Xvalue->index, LA_ZERO);
|
||||
la_slli_d(&as, Xscratch0, Xscratch0, 31);
|
||||
la_or(&as, Xnz->index, Xnz->index, Xscratch0);
|
||||
}
|
||||
|
||||
EmittedBlockInfo EmitLoongArch64(lagoon_assembler_t& as, IR::Block block, const EmitConfig& emit_conf) {
|
||||
EmittedBlockInfo ebi;
|
||||
|
||||
RegAlloc reg_alloc{as, std::vector<u32>(GPR_ORDER), std::vector<u32>(FPR_ORDER)};
|
||||
EmitContext ctx{block, reg_alloc, emit_conf, ebi};
|
||||
|
||||
ebi.entry_point = reinterpret_cast<CodePtr>(as.cursor);
|
||||
|
||||
for (auto iter = block.instructions.begin(); iter != block.instructions.end(); ++iter) {
|
||||
IR::Inst* inst = &*iter;
|
||||
|
||||
switch (inst->GetOpcode()) {
|
||||
#define OPCODE(name, type, ...) \
|
||||
case IR::Opcode::name: \
|
||||
EmitIR<IR::Opcode::name>(as, ctx, inst); \
|
||||
break;
|
||||
#define A32OPC(name, type, ...) \
|
||||
case IR::Opcode::A32##name: \
|
||||
EmitIR<IR::Opcode::A32##name>(as, ctx, inst); \
|
||||
break;
|
||||
#define A64OPC(name, type, ...) \
|
||||
case IR::Opcode::A64##name: \
|
||||
EmitIR<IR::Opcode::A64##name>(as, ctx, inst); \
|
||||
break;
|
||||
#include "dynarmic/ir/opcodes.inc"
|
||||
#undef OPCODE
|
||||
#undef A32OPC
|
||||
#undef A64OPC
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
reg_alloc.UpdateAllUses();
|
||||
}
|
||||
|
||||
reg_alloc.UpdateAllUses();
|
||||
reg_alloc.AssertNoMoreUses();
|
||||
|
||||
// TODO: Emit Terminal
|
||||
const auto term = block.GetTerminal();
|
||||
const IR::Term::LinkBlock* link_block_term = boost::get<IR::Term::LinkBlock>(&term);
|
||||
ASSERT(link_block_term);
|
||||
la_load_immediate64(&as, Xscratch0, link_block_term->next.Value());
|
||||
la_st_w(&as, Xscratch0, Xstate, static_cast<int32_t>(offsetof(A32JitState, regs) + sizeof(u32) * 15));
|
||||
|
||||
ebi.relocations.push_back(Relocation{
|
||||
reinterpret_cast<CodePtr>(as.cursor) - ebi.entry_point,
|
||||
LinkTarget::ReturnFromRunCode
|
||||
});
|
||||
la_nop(&as);
|
||||
|
||||
ebi.size = reinterpret_cast<CodePtr>(as.cursor) - ebi.entry_point;
|
||||
return ebi;
|
||||
}
|
||||
|
||||
} // namespace Dynarmic::Backend::LoongArch64
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
#include "dynarmic/backend/loongarch64/lagoon_cpp.h"
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Dynarmic::IR {
|
||||
class Block;
|
||||
class Inst;
|
||||
class LocationDescriptor;
|
||||
enum class Cond;
|
||||
enum class Opcode;
|
||||
} // namespace Dynarmic::IR
|
||||
|
||||
namespace Dynarmic::A32 {
|
||||
class Coprocessor;
|
||||
} // namespace Dynarmic::A32
|
||||
|
||||
namespace Dynarmic::Backend::LoongArch64 {
|
||||
|
||||
using CodePtr = std::byte*;
|
||||
|
||||
enum class LinkTarget {
|
||||
ReturnFromRunCode,
|
||||
};
|
||||
|
||||
struct Relocation {
|
||||
std::ptrdiff_t code_offset;
|
||||
LinkTarget target;
|
||||
};
|
||||
|
||||
struct EmittedBlockInfo {
|
||||
std::vector<Relocation> relocations;
|
||||
CodePtr entry_point;
|
||||
size_t size;
|
||||
size_t cycle_count;
|
||||
};
|
||||
|
||||
struct EmitConfig {};
|
||||
|
||||
struct EmitContext;
|
||||
|
||||
template<IR::Opcode op>
|
||||
void EmitIR(lagoon_assembler_t& as, EmitContext& ctx, IR::Inst* inst);
|
||||
|
||||
EmittedBlockInfo EmitLoongArch64(lagoon_assembler_t& as, IR::Block block, const EmitConfig& emit_conf);
|
||||
|
||||
} // namespace Dynarmic::Backend::LoongArch64
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "dynarmic/backend/loongarch64/lagoon_cpp.h"
|
||||
|
||||
#include "dynarmic/backend/loongarch64/a32_jitstate.h"
|
||||
#include "dynarmic/backend/loongarch64/abi.h"
|
||||
#include "dynarmic/backend/loongarch64/emit_context.h"
|
||||
#include "dynarmic/backend/loongarch64/emit_loongarch64.h"
|
||||
#include "dynarmic/backend/loongarch64/reg_alloc.h"
|
||||
#include "dynarmic/ir/basic_block.h"
|
||||
#include "dynarmic/ir/microinstruction.h"
|
||||
#include "dynarmic/ir/opcodes.h"
|
||||
|
||||
namespace Dynarmic::Backend::LoongArch64 {
|
||||
|
||||
template<>
|
||||
void EmitIR<IR::Opcode::A32GetRegister>(lagoon_assembler_t& as, EmitContext& ctx, IR::Inst* inst) {
|
||||
const A32::Reg reg = inst->GetArg(0).GetA32RegRef();
|
||||
|
||||
auto Xresult = ctx.reg_alloc.WriteX(inst);
|
||||
RegAlloc::Realize(Xresult);
|
||||
|
||||
la_ld_wu(&as, Xresult->index, Xstate,
|
||||
static_cast<int32_t>(offsetof(A32JitState, regs) + sizeof(u32) * static_cast<size_t>(reg)));
|
||||
}
|
||||
|
||||
template<>
|
||||
void EmitIR<IR::Opcode::A32SetRegister>(lagoon_assembler_t& as, EmitContext& ctx, IR::Inst* inst) {
|
||||
const A32::Reg reg = inst->GetArg(0).GetA32RegRef();
|
||||
|
||||
auto args = ctx.reg_alloc.GetArgumentInfo(inst);
|
||||
auto Xvalue = ctx.reg_alloc.ReadX(args[1]);
|
||||
RegAlloc::Realize(Xvalue);
|
||||
|
||||
la_st_w(&as, Xvalue->index, Xstate,
|
||||
static_cast<int32_t>(offsetof(A32JitState, regs) + sizeof(u32) * static_cast<size_t>(reg)));
|
||||
}
|
||||
|
||||
template<>
|
||||
void EmitIR<IR::Opcode::A32SetCpsrNZC>(lagoon_assembler_t& as, EmitContext& ctx, IR::Inst* inst) {
|
||||
auto args = ctx.reg_alloc.GetArgumentInfo(inst);
|
||||
|
||||
ASSERT(!args[0].IsImmediate() && !args[1].IsImmediate());
|
||||
|
||||
auto Xnz = ctx.reg_alloc.ReadX(args[0]);
|
||||
auto Xc = ctx.reg_alloc.ReadX(args[1]);
|
||||
RegAlloc::Realize(Xnz, Xc);
|
||||
|
||||
la_ld_wu(&as, Xscratch0, Xstate, static_cast<int32_t>(offsetof(A32JitState, cpsr_nzcv)));
|
||||
la_load_immediate64(&as, Xscratch1, 0x10000000);
|
||||
la_and(&as, Xscratch0, Xscratch0, Xscratch1);
|
||||
la_or(&as, Xscratch0, Xscratch0, Xnz->index);
|
||||
la_slli_w(&as, Xscratch1, Xc->index, 29);
|
||||
la_or(&as, Xscratch0, Xscratch0, Xscratch1);
|
||||
la_st_w(&as, Xscratch0, Xstate, static_cast<int32_t>(offsetof(A32JitState, cpsr_nzcv)));
|
||||
}
|
||||
|
||||
} // namespace Dynarmic::Backend::LoongArch64
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "dynarmic/backend/loongarch64/lagoon_cpp.h"
|
||||
|
||||
#include "dynarmic/backend/loongarch64/abi.h"
|
||||
#include "dynarmic/backend/loongarch64/emit_context.h"
|
||||
#include "dynarmic/backend/loongarch64/emit_loongarch64.h"
|
||||
#include "dynarmic/backend/loongarch64/reg_alloc.h"
|
||||
#include "dynarmic/ir/basic_block.h"
|
||||
#include "dynarmic/ir/microinstruction.h"
|
||||
#include "dynarmic/ir/opcodes.h"
|
||||
|
||||
namespace Dynarmic::Backend::LoongArch64 {
|
||||
|
||||
template<>
|
||||
void EmitIR<IR::Opcode::LogicalShiftLeft32>(lagoon_assembler_t& as, EmitContext& ctx, IR::Inst* inst) {
|
||||
const auto carry_inst = inst->GetAssociatedPseudoOperation(IR::Opcode::GetCarryFromOp);
|
||||
|
||||
auto args = ctx.reg_alloc.GetArgumentInfo(inst);
|
||||
auto& operand_arg = args[0];
|
||||
auto& shift_arg = args[1];
|
||||
auto& carry_arg = args[2];
|
||||
|
||||
ASSERT(carry_inst != nullptr);
|
||||
ASSERT(shift_arg.IsImmediate());
|
||||
|
||||
auto Xresult = ctx.reg_alloc.WriteX(inst);
|
||||
auto Xcarry_out = ctx.reg_alloc.WriteX(carry_inst);
|
||||
auto Xoperand = ctx.reg_alloc.ReadX(operand_arg);
|
||||
auto Xcarry_in = ctx.reg_alloc.ReadX(carry_arg);
|
||||
RegAlloc::Realize(Xresult, Xcarry_out, Xoperand, Xcarry_in);
|
||||
|
||||
const u8 shift = shift_arg.GetImmediateU8();
|
||||
|
||||
if (shift == 0) {
|
||||
la_addi_w(&as, Xresult->index, Xoperand->index, 0);
|
||||
la_addi_w(&as, Xcarry_out->index, Xcarry_in->index, 0);
|
||||
} else if (shift < 32) {
|
||||
la_srli_w(&as, Xcarry_out->index, Xoperand->index, 32 - shift);
|
||||
la_andi(&as, Xcarry_out->index, Xcarry_out->index, 1);
|
||||
la_slli_w(&as, Xresult->index, Xoperand->index, shift);
|
||||
} else if (shift > 32) {
|
||||
la_move(&as, Xresult->index, LA_ZERO);
|
||||
la_move(&as, Xcarry_out->index, LA_ZERO);
|
||||
} else {
|
||||
la_andi(&as, Xcarry_out->index, Xoperand->index, 1);
|
||||
la_move(&as, Xresult->index, LA_ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Dynarmic::Backend::LoongArch64
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
extern "C" {
|
||||
#include <lagoon.h>
|
||||
}
|
||||
364
src/dynarmic/src/dynarmic/backend/loongarch64/reg_alloc.cpp
Normal file
364
src/dynarmic/src/dynarmic/backend/loongarch64/reg_alloc.cpp
Normal file
|
|
@ -0,0 +1,364 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "dynarmic/backend/loongarch64/reg_alloc.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <limits>
|
||||
|
||||
#include "dynarmic/backend/loongarch64/lagoon_cpp.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
#include "dynarmic/common/always_false.h"
|
||||
|
||||
namespace Dynarmic::Backend::LoongArch64 {
|
||||
|
||||
constexpr size_t spill_offset = offsetof(StackLayout, spill);
|
||||
constexpr size_t spill_slot_size = sizeof(decltype(StackLayout::spill)::value_type);
|
||||
|
||||
static bool IsValuelessType(IR::Type type) {
|
||||
switch (type) {
|
||||
case IR::Type::Table:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
IR::Type Argument::GetType() const {
|
||||
return value.GetType();
|
||||
}
|
||||
|
||||
bool Argument::IsImmediate() const {
|
||||
return value.IsImmediate();
|
||||
}
|
||||
|
||||
bool Argument::GetImmediateU1() const {
|
||||
return value.GetU1();
|
||||
}
|
||||
|
||||
u8 Argument::GetImmediateU8() const {
|
||||
const u64 imm = value.GetImmediateAsU64();
|
||||
ASSERT(imm < 0x100);
|
||||
return u8(imm);
|
||||
}
|
||||
|
||||
u16 Argument::GetImmediateU16() const {
|
||||
const u64 imm = value.GetImmediateAsU64();
|
||||
ASSERT(imm < 0x10000);
|
||||
return u16(imm);
|
||||
}
|
||||
|
||||
u32 Argument::GetImmediateU32() const {
|
||||
const u64 imm = value.GetImmediateAsU64();
|
||||
ASSERT(imm < 0x100000000);
|
||||
return u32(imm);
|
||||
}
|
||||
|
||||
u64 Argument::GetImmediateU64() const {
|
||||
return value.GetImmediateAsU64();
|
||||
}
|
||||
|
||||
IR::Cond Argument::GetImmediateCond() const {
|
||||
ASSERT(IsImmediate() && GetType() == IR::Type::Cond);
|
||||
return value.GetCond();
|
||||
}
|
||||
|
||||
IR::AccType Argument::GetImmediateAccType() const {
|
||||
ASSERT(IsImmediate() && GetType() == IR::Type::AccType);
|
||||
return value.GetAccType();
|
||||
}
|
||||
|
||||
bool HostLocInfo::Contains(const IR::Inst* value) const {
|
||||
return std::find(values.begin(), values.end(), value) != values.end();
|
||||
}
|
||||
|
||||
void HostLocInfo::SetupScratchLocation() {
|
||||
ASSERT(IsCompletelyEmpty());
|
||||
locked = 1;
|
||||
realized = true;
|
||||
}
|
||||
|
||||
bool HostLocInfo::IsCompletelyEmpty() const {
|
||||
return values.empty() && !locked && !accumulated_uses && !expected_uses && !uses_this_inst && !realized;
|
||||
}
|
||||
|
||||
void HostLocInfo::UpdateUses() {
|
||||
accumulated_uses += uses_this_inst;
|
||||
uses_this_inst = 0;
|
||||
|
||||
if (accumulated_uses == expected_uses) {
|
||||
values.clear();
|
||||
accumulated_uses = 0;
|
||||
expected_uses = 0;
|
||||
}
|
||||
}
|
||||
|
||||
RegAlloc::ArgumentInfo RegAlloc::GetArgumentInfo(IR::Inst* inst) {
|
||||
ArgumentInfo ret = {Argument{}, Argument{}, Argument{}, Argument{}};
|
||||
for (size_t i = 0; i < inst->NumArgs(); i++) {
|
||||
const IR::Value arg = inst->GetArg(i);
|
||||
ret[i].value = arg;
|
||||
if (!arg.IsImmediate() && !IsValuelessType(arg.GetType())) {
|
||||
ASSERT(ValueLocation(arg.GetInst()) && "argument must already been defined");
|
||||
ValueInfo(arg.GetInst()).uses_this_inst++;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool RegAlloc::IsValueLive(IR::Inst* inst) const {
|
||||
return !!ValueLocation(inst);
|
||||
}
|
||||
|
||||
void RegAlloc::UpdateAllUses() {
|
||||
for (auto& info : hostloc_info) {
|
||||
info.UpdateUses();
|
||||
}
|
||||
}
|
||||
|
||||
void RegAlloc::DefineAsExisting(IR::Inst* inst, Argument& arg) {
|
||||
ASSERT(!ValueLocation(inst));
|
||||
|
||||
if (arg.value.IsImmediate()) {
|
||||
inst->ReplaceUsesWith(arg.value);
|
||||
return;
|
||||
}
|
||||
|
||||
auto& info = ValueInfo(arg.value.GetInst());
|
||||
info.values.emplace_back(inst);
|
||||
info.expected_uses += inst->UseCount();
|
||||
}
|
||||
|
||||
void RegAlloc::AssertNoMoreUses() const {
|
||||
// TODO: Re-enable this assert once all register allocation issues are fixed
|
||||
// const auto is_empty = [](const auto& i) { return i.IsCompletelyEmpty(); };
|
||||
// ASSERT(std::all_of(hostloc_info.begin(), hostloc_info.end(), is_empty));
|
||||
}
|
||||
|
||||
template<HostLoc::Kind kind>
|
||||
u32 RegAlloc::GenerateImmediate(const IR::Value& value) {
|
||||
if constexpr (kind == HostLoc::Kind::Gpr) {
|
||||
const u32 new_location_index = AllocateRegister(gpr_order, GprOffset);
|
||||
SpillGpr(new_location_index);
|
||||
hostloc_info[GprOffset + new_location_index].SetupScratchLocation();
|
||||
|
||||
la_load_immediate64(&as, static_cast<la_gpr_t>(new_location_index),
|
||||
static_cast<int64_t>(value.GetImmediateAsU64()));
|
||||
|
||||
return new_location_index;
|
||||
} else if constexpr (kind == HostLoc::Kind::Fpr) {
|
||||
ASSERT(false && "Unimplemented instruction");
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<HostLoc::Kind required_kind>
|
||||
u32 RegAlloc::RealizeReadImpl(const IR::Value& value) {
|
||||
if (value.IsImmediate()) {
|
||||
return GenerateImmediate<required_kind>(value);
|
||||
}
|
||||
|
||||
const auto current_location = ValueLocation(value.GetInst());
|
||||
ASSERT(current_location);
|
||||
|
||||
if (current_location->kind == required_kind) {
|
||||
ValueInfo(*current_location).realized = true;
|
||||
return current_location->index;
|
||||
}
|
||||
|
||||
ASSERT(!ValueInfo(*current_location).realized);
|
||||
ASSERT(!ValueInfo(*current_location).locked);
|
||||
|
||||
if constexpr (required_kind == HostLoc::Kind::Gpr) {
|
||||
const u32 new_location_index = AllocateRegister(gpr_order, GprOffset);
|
||||
SpillGpr(new_location_index);
|
||||
|
||||
switch (current_location->kind) {
|
||||
case HostLoc::Kind::Gpr:
|
||||
UNREACHABLE();
|
||||
case HostLoc::Kind::Fpr:
|
||||
la_movfr2gr_d(&as, static_cast<la_gpr_t>(new_location_index),
|
||||
static_cast<la_fpr_t>(current_location->index));
|
||||
break;
|
||||
case HostLoc::Kind::Spill:
|
||||
la_ld_d(&as, static_cast<la_gpr_t>(new_location_index), LA_SP,
|
||||
static_cast<int32_t>(spill_offset + current_location->index * spill_slot_size));
|
||||
break;
|
||||
}
|
||||
|
||||
hostloc_info[GprOffset + new_location_index] = std::exchange(ValueInfo(*current_location), {});
|
||||
hostloc_info[GprOffset + new_location_index].realized = true;
|
||||
return new_location_index;
|
||||
} else if constexpr (required_kind == HostLoc::Kind::Fpr) {
|
||||
const u32 new_location_index = AllocateRegister(fpr_order, FprOffset);
|
||||
SpillFpr(new_location_index);
|
||||
|
||||
switch (current_location->kind) {
|
||||
case HostLoc::Kind::Gpr:
|
||||
la_movgr2fr_d(&as, static_cast<la_fpr_t>(new_location_index),
|
||||
static_cast<la_gpr_t>(current_location->index));
|
||||
break;
|
||||
case HostLoc::Kind::Fpr:
|
||||
UNREACHABLE();
|
||||
case HostLoc::Kind::Spill:
|
||||
la_fld_d(&as, static_cast<la_fpr_t>(new_location_index), LA_SP,
|
||||
static_cast<int32_t>(spill_offset + current_location->index * spill_slot_size));
|
||||
break;
|
||||
}
|
||||
|
||||
hostloc_info[FprOffset + new_location_index] = std::exchange(ValueInfo(*current_location), {});
|
||||
hostloc_info[FprOffset + new_location_index].realized = true;
|
||||
return new_location_index;
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
u32 RegAlloc::RealizeWriteImpl(const IR::Inst* value, HostLoc::Kind required_kind) {
|
||||
if (value == nullptr) {
|
||||
// Scratch register allocation
|
||||
if (required_kind == HostLoc::Kind::Gpr) {
|
||||
const u32 idx = AllocateRegister(gpr_order, GprOffset);
|
||||
SpillGpr(idx);
|
||||
hostloc_info[GprOffset + idx].SetupScratchLocation();
|
||||
return idx;
|
||||
} else if (required_kind == HostLoc::Kind::Fpr) {
|
||||
const u32 idx = AllocateRegister(fpr_order, FprOffset);
|
||||
SpillFpr(idx);
|
||||
hostloc_info[FprOffset + idx].SetupScratchLocation();
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT(!ValueLocation(value));
|
||||
|
||||
const auto setup_location = [&](HostLocInfo& info) {
|
||||
info = {};
|
||||
info.values.emplace_back(value);
|
||||
info.locked = true;
|
||||
info.realized = true;
|
||||
info.expected_uses = value->UseCount();
|
||||
};
|
||||
|
||||
if (required_kind == HostLoc::Kind::Gpr) {
|
||||
const u32 new_location_index = AllocateRegister(gpr_order, GprOffset);
|
||||
SpillGpr(new_location_index);
|
||||
setup_location(hostloc_info[GprOffset + new_location_index]);
|
||||
return new_location_index;
|
||||
} else if (required_kind == HostLoc::Kind::Fpr) {
|
||||
const u32 new_location_index = AllocateRegister(fpr_order, FprOffset);
|
||||
SpillFpr(new_location_index);
|
||||
setup_location(hostloc_info[FprOffset + new_location_index]);
|
||||
return new_location_index;
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
template u32 RegAlloc::RealizeReadImpl<HostLoc::Kind::Gpr>(const IR::Value& value);
|
||||
template u32 RegAlloc::RealizeReadImpl<HostLoc::Kind::Fpr>(const IR::Value& value);
|
||||
|
||||
u32 RegAlloc::AllocateRegister(const std::vector<u32>& order, size_t base_offset) {
|
||||
const auto empty = std::find_if(order.begin(), order.end(), [&](u32 i) {
|
||||
auto& info = hostloc_info[base_offset + i];
|
||||
return info.values.empty() && !info.locked;
|
||||
});
|
||||
if (empty != order.end()) {
|
||||
return *empty;
|
||||
}
|
||||
|
||||
std::vector<u32> candidates;
|
||||
std::copy_if(order.begin(), order.end(), std::back_inserter(candidates), [&](u32 i) {
|
||||
return !hostloc_info[base_offset + i].locked;
|
||||
});
|
||||
ASSERT(!candidates.empty());
|
||||
|
||||
u32 best = candidates[0];
|
||||
size_t min_lru = hostloc_info[base_offset + best].lru_counter;
|
||||
for (size_t i = 1; i < candidates.size(); ++i) {
|
||||
auto& info = hostloc_info[base_offset + candidates[i]];
|
||||
if (info.lru_counter < min_lru) {
|
||||
min_lru = info.lru_counter;
|
||||
best = candidates[i];
|
||||
}
|
||||
}
|
||||
hostloc_info[base_offset + best].lru_counter++;
|
||||
return best;
|
||||
}
|
||||
|
||||
void RegAlloc::SpillGpr(u32 index) {
|
||||
auto& gpr_info = hostloc_info[GprOffset + index];
|
||||
ASSERT(!gpr_info.locked && !gpr_info.realized);
|
||||
if (gpr_info.values.empty()) {
|
||||
return;
|
||||
}
|
||||
const u32 new_location_index = FindFreeSpill();
|
||||
la_st_d(&as, static_cast<la_gpr_t>(index), LA_SP,
|
||||
static_cast<int32_t>(spill_offset + new_location_index * spill_slot_size));
|
||||
hostloc_info[SpillOffset + new_location_index] = std::exchange(gpr_info, {});
|
||||
}
|
||||
|
||||
void RegAlloc::SpillFpr(u32 index) {
|
||||
auto& fpr_info = hostloc_info[FprOffset + index];
|
||||
ASSERT(!fpr_info.locked && !fpr_info.realized);
|
||||
if (fpr_info.values.empty()) {
|
||||
return;
|
||||
}
|
||||
const u32 new_location_index = FindFreeSpill();
|
||||
la_fst_d(&as, static_cast<la_fpr_t>(index), LA_SP,
|
||||
static_cast<int32_t>(spill_offset + new_location_index * spill_slot_size));
|
||||
hostloc_info[SpillOffset + new_location_index] = std::exchange(fpr_info, {});
|
||||
}
|
||||
|
||||
u32 RegAlloc::FindFreeSpill() const {
|
||||
for (size_t i = 0; i < SpillCount; ++i) {
|
||||
if (hostloc_info[SpillOffset + i].values.empty()) {
|
||||
return static_cast<u32>(i);
|
||||
}
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
std::optional<HostLoc> RegAlloc::ValueLocation(const IR::Inst* value) const {
|
||||
for (size_t i = 0; i < hostloc_info.size(); ++i) {
|
||||
if (hostloc_info[i].Contains(value)) {
|
||||
if (i < GprCount) {
|
||||
return HostLoc{HostLoc::Kind::Gpr, static_cast<u32>(i)};
|
||||
} else if (i < GprCount + FprCount) {
|
||||
return HostLoc{HostLoc::Kind::Fpr, static_cast<u32>(i - GprCount)};
|
||||
} else {
|
||||
return HostLoc{HostLoc::Kind::Spill, static_cast<u32>(i - GprCount - FprCount)};
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
HostLocInfo& RegAlloc::ValueInfo(HostLoc host_loc) {
|
||||
switch (host_loc.kind) {
|
||||
case HostLoc::Kind::Gpr:
|
||||
return hostloc_info[GprOffset + host_loc.index];
|
||||
case HostLoc::Kind::Fpr:
|
||||
return hostloc_info[FprOffset + host_loc.index];
|
||||
case HostLoc::Kind::Spill:
|
||||
return hostloc_info[SpillOffset + host_loc.index];
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
HostLocInfo& RegAlloc::ValueInfo(const IR::Inst* value) {
|
||||
for (auto& info : hostloc_info) {
|
||||
if (info.Contains(value)) {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
} // namespace Dynarmic::Backend::LoongArch64
|
||||
232
src/dynarmic/src/dynarmic/backend/loongarch64/reg_alloc.h
Normal file
232
src/dynarmic/src/dynarmic/backend/loongarch64/reg_alloc.h
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "dynarmic/backend/loongarch64/lagoon_cpp.h"
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "dynarmic/mcl/is_instance_of_template.hpp"
|
||||
|
||||
#include "dynarmic/backend/loongarch64/stack_layout.h"
|
||||
#include "dynarmic/ir/cond.h"
|
||||
#include "dynarmic/ir/microinstruction.h"
|
||||
#include "dynarmic/ir/value.h"
|
||||
|
||||
namespace Dynarmic::Backend::LoongArch64 {
|
||||
|
||||
class RegAlloc;
|
||||
|
||||
// Wrapper types for LoongArch GPR/FPR (replacing biscuit's register types)
|
||||
struct GPR {
|
||||
la_gpr_t index = LA_ZERO;
|
||||
GPR() = default;
|
||||
explicit GPR(u32 i) : index{static_cast<la_gpr_t>(i)} {}
|
||||
uint32_t Index() const { return static_cast<uint32_t>(index); }
|
||||
};
|
||||
|
||||
struct FPR {
|
||||
la_fpr_t index = LA_F0;
|
||||
FPR() = default;
|
||||
explicit FPR(u32 i) : index{static_cast<la_fpr_t>(i)} {}
|
||||
uint32_t Index() const { return static_cast<uint32_t>(index); }
|
||||
};
|
||||
|
||||
struct VPR {
|
||||
la_vpr_t index;
|
||||
VPR() = default;
|
||||
explicit VPR(u32 i) : index{static_cast<la_vpr_t>(i)} {}
|
||||
uint32_t Index() const { return static_cast<uint32_t>(index); }
|
||||
};
|
||||
|
||||
struct HostLoc {
|
||||
enum class Kind {
|
||||
Gpr,
|
||||
Fpr,
|
||||
Spill,
|
||||
} kind;
|
||||
u32 index;
|
||||
};
|
||||
|
||||
struct Argument {
|
||||
public:
|
||||
using copyable_reference = std::reference_wrapper<Argument>;
|
||||
|
||||
IR::Type GetType() const;
|
||||
bool IsImmediate() const;
|
||||
|
||||
bool GetImmediateU1() const;
|
||||
u8 GetImmediateU8() const;
|
||||
u16 GetImmediateU16() const;
|
||||
u32 GetImmediateU32() const;
|
||||
u64 GetImmediateU64() const;
|
||||
IR::Cond GetImmediateCond() const;
|
||||
IR::AccType GetImmediateAccType() const;
|
||||
|
||||
private:
|
||||
friend class RegAlloc;
|
||||
explicit Argument() {}
|
||||
|
||||
IR::Value value;
|
||||
bool allocated = false;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct RAReg {
|
||||
public:
|
||||
static constexpr HostLoc::Kind kind = std::is_same_v<T, FPR> || std::is_same_v<T, VPR>
|
||||
? HostLoc::Kind::Fpr
|
||||
: HostLoc::Kind::Gpr;
|
||||
|
||||
operator T() const { return *reg; }
|
||||
|
||||
T operator*() const { return *reg; }
|
||||
|
||||
const T* operator->() const { return &*reg; }
|
||||
|
||||
~RAReg();
|
||||
|
||||
private:
|
||||
friend class RegAlloc;
|
||||
explicit RAReg(RegAlloc& reg_alloc, bool write, const IR::Value& value);
|
||||
|
||||
void Realize();
|
||||
|
||||
RegAlloc& reg_alloc;
|
||||
bool write;
|
||||
const IR::Value value;
|
||||
std::optional<T> reg;
|
||||
};
|
||||
|
||||
struct HostLocInfo final {
|
||||
std::vector<const IR::Inst*> values;
|
||||
size_t locked = 0;
|
||||
size_t uses_this_inst = 0;
|
||||
size_t accumulated_uses = 0;
|
||||
size_t expected_uses = 0;
|
||||
bool realized = false;
|
||||
size_t lru_counter = 0;
|
||||
|
||||
bool Contains(const IR::Inst*) const;
|
||||
void SetupScratchLocation();
|
||||
bool IsCompletelyEmpty() const;
|
||||
void UpdateUses();
|
||||
};
|
||||
|
||||
class RegAlloc {
|
||||
public:
|
||||
using ArgumentInfo = std::array<Argument, IR::max_arg_count>;
|
||||
|
||||
explicit RegAlloc(lagoon_assembler_t& as, std::vector<u32> gpr_order, std::vector<u32> fpr_order)
|
||||
: as{as}, gpr_order{std::move(gpr_order)}, fpr_order{std::move(fpr_order)} {}
|
||||
|
||||
ArgumentInfo GetArgumentInfo(IR::Inst* inst);
|
||||
bool IsValueLive(IR::Inst* inst) const;
|
||||
|
||||
auto ReadX(Argument& arg) { return RAReg<GPR>{*this, false, arg.value}; }
|
||||
auto ReadD(Argument& arg) { return RAReg<FPR>{*this, false, arg.value}; }
|
||||
auto ReadV(Argument& arg) { return RAReg<VPR>{*this, false, arg.value}; }
|
||||
|
||||
auto WriteX(IR::Inst* inst) { return RAReg<GPR>{*this, true, inst ? IR::Value{inst} : IR::Value{}}; }
|
||||
auto WriteD(IR::Inst* inst) { return RAReg<FPR>{*this, true, inst ? IR::Value{inst} : IR::Value{}}; }
|
||||
auto WriteV(IR::Inst* inst) { return RAReg<VPR>{*this, true, inst ? IR::Value{inst} : IR::Value{}}; }
|
||||
|
||||
auto ScratchGpr() { return RAReg<GPR>{*this, true, IR::Value{}}; }
|
||||
auto ScratchFpr() { return RAReg<FPR>{*this, true, IR::Value{}}; }
|
||||
auto ScratchVec() { return RAReg<VPR>{*this, true, IR::Value{}}; }
|
||||
|
||||
void DefineAsExisting(IR::Inst* inst, Argument& arg);
|
||||
|
||||
template<typename... Ts>
|
||||
static void Realize(Ts&... rs) {
|
||||
static_assert((mcl::is_instance_of_template<RAReg, Ts>() && ...));
|
||||
(rs.Realize(), ...);
|
||||
}
|
||||
|
||||
void UpdateAllUses();
|
||||
void AssertNoMoreUses() const;
|
||||
|
||||
private:
|
||||
template<typename>
|
||||
friend struct RAReg;
|
||||
|
||||
template<HostLoc::Kind kind>
|
||||
u32 GenerateImmediate(const IR::Value& value);
|
||||
template<HostLoc::Kind kind>
|
||||
u32 RealizeReadImpl(const IR::Value& value);
|
||||
u32 RealizeWriteImpl(const IR::Inst* value, HostLoc::Kind required_kind);
|
||||
|
||||
u32 AllocateRegister(const std::vector<u32>& order, size_t base_offset);
|
||||
void SpillGpr(u32 index);
|
||||
void SpillFpr(u32 index);
|
||||
u32 FindFreeSpill() const;
|
||||
|
||||
std::optional<HostLoc> ValueLocation(const IR::Inst* value) const;
|
||||
HostLocInfo& ValueInfo(HostLoc host_loc);
|
||||
HostLocInfo& ValueInfo(const IR::Inst* value);
|
||||
|
||||
lagoon_assembler_t& as;
|
||||
std::vector<u32> gpr_order;
|
||||
std::vector<u32> fpr_order;
|
||||
|
||||
static constexpr size_t GprCount = 32;
|
||||
static constexpr size_t FprCount = 32;
|
||||
static constexpr size_t GprOffset = 0;
|
||||
static constexpr size_t FprOffset = GprCount;
|
||||
static constexpr size_t SpillOffset = GprCount + FprCount;
|
||||
|
||||
std::array<HostLocInfo, GprCount + FprCount + SpillCount> hostloc_info;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
RAReg<T>::RAReg(RegAlloc& reg_alloc, bool write, const IR::Value& value)
|
||||
: reg_alloc{reg_alloc}, write{write}, value{value} {
|
||||
if (!write && !value.IsImmediate()) {
|
||||
reg_alloc.ValueInfo(value.GetInst()).locked++;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
RAReg<T>::~RAReg() {
|
||||
if (value.IsEmpty()) {
|
||||
if (reg) {
|
||||
HostLocInfo& info = reg_alloc.ValueInfo(HostLoc{kind, reg->Index()});
|
||||
info.locked--;
|
||||
info.realized = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (value.IsImmediate()) {
|
||||
if (reg) {
|
||||
// Immediate was materialized into a scratch register
|
||||
HostLocInfo& info = reg_alloc.ValueInfo(HostLoc{kind, reg->Index()});
|
||||
info.locked--;
|
||||
info.realized = false;
|
||||
}
|
||||
} else if (!value.IsEmpty()) {
|
||||
HostLocInfo& info = reg_alloc.ValueInfo(value.GetInst());
|
||||
info.locked--;
|
||||
if (reg) {
|
||||
info.realized = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void RAReg<T>::Realize() {
|
||||
if (write && value.IsEmpty()) {
|
||||
reg = T{reg_alloc.RealizeWriteImpl(nullptr, kind)};
|
||||
} else {
|
||||
reg = T{write ? reg_alloc.RealizeWriteImpl(value.GetInst(), kind) : reg_alloc.RealizeReadImpl<kind>(value)};
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Dynarmic::Backend::LoongArch64
|
||||
28
src/dynarmic/src/dynarmic/backend/loongarch64/stack_layout.h
Normal file
28
src/dynarmic/src/dynarmic/backend/loongarch64/stack_layout.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Dynarmic::Backend::LoongArch64 {
|
||||
|
||||
constexpr size_t SpillCount = 64;
|
||||
|
||||
struct alignas(16) StackLayout {
|
||||
s64 cycles_remaining;
|
||||
s64 cycles_to_run;
|
||||
|
||||
std::array<u64, SpillCount> spill;
|
||||
|
||||
u32 save_host_fpcr;
|
||||
u32 save_host_fpsr;
|
||||
|
||||
bool check_bit;
|
||||
};
|
||||
|
||||
static_assert(sizeof(StackLayout) % 16 == 0);
|
||||
|
||||
} // namespace Dynarmic::Backend::LoongArch64
|
||||
|
|
@ -75,7 +75,7 @@ ArgCallback DevirtualizeItanium(mcl::class_type<decltype(mfp)>* this_) {
|
|||
|
||||
template<auto mfp>
|
||||
ArgCallback Devirtualize(mcl::class_type<decltype(mfp)>* this_) {
|
||||
#if defined(__APPLE__) || defined(linux) || defined(__linux) || defined(__linux__)
|
||||
#if defined(__APPLE__) || defined(__linux__)
|
||||
return DevirtualizeItanium<mfp>(this_);
|
||||
#elif defined(__MINGW64__)
|
||||
return DevirtualizeItanium<mfp>(this_);
|
||||
|
|
|
|||
|
|
@ -36,6 +36,14 @@ inline void And(volatile u32* ptr, u32 value) {
|
|||
#endif
|
||||
}
|
||||
|
||||
inline u32 Exchange(volatile u32* ptr, u32 value) {
|
||||
#ifdef _MSC_VER
|
||||
return static_cast<u32>(_InterlockedExchange(reinterpret_cast<volatile long*>(ptr), value));
|
||||
#else
|
||||
return __atomic_exchange_n(ptr, value, __ATOMIC_SEQ_CST);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void Barrier() {
|
||||
#ifdef _MSC_VER
|
||||
_ReadWriteBarrier();
|
||||
|
|
|
|||
|
|
@ -1009,7 +1009,7 @@ std::string Config::AdjustOutputString(const std::string& string) {
|
|||
|
||||
// Windows requires that two forward slashes are used at the start of a path for unmapped
|
||||
// network drives so we have to watch for that here
|
||||
#ifndef ANDROID
|
||||
#ifndef __ANDROID__
|
||||
if (string.substr(0, 2) == "//") {
|
||||
boost::replace_all(adjusted_string, "//", "/");
|
||||
adjusted_string.insert(0, "/");
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef DATA_MANAGER_H
|
||||
#define DATA_MANAGER_H
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include <string>
|
||||
|
|
@ -21,5 +20,3 @@ std::string ReadableBytesSize(u64 size) noexcept;
|
|||
u64 DataDirSize(DataDir dir);
|
||||
|
||||
}; // namespace FrontendCommon::DataManager
|
||||
|
||||
#endif // DATA_MANAGER_H
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
#include "core/crypto/key_manager.h"
|
||||
#include "frontend_common/content_manager.h"
|
||||
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
#include <jni.h>
|
||||
#include <common/android/id_cache.h>
|
||||
#include <common/android/android_common.h>
|
||||
|
|
@ -25,7 +25,7 @@ FirmwareManager::InstallKeys(std::string location, std::string extension) {
|
|||
|
||||
const auto keys_dir = Common::FS::GetEdenPath(Common::FS::EdenPath::KeysDir);
|
||||
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
JNIEnv *env = Common::Android::GetEnvForThread();
|
||||
|
||||
jstring jsrc = Common::Android::ToJString(env, location);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef FIRMWARE_MANAGER_H
|
||||
#define FIRMWARE_MANAGER_H
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/core.h"
|
||||
|
|
@ -107,5 +106,3 @@ inline std::pair<Service::Set::FirmwareVersionFormat, Result> GetFirmwareVersion
|
|||
|
||||
// TODO(crueter): GET AS STRING
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace FrontendCommon {
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ void EmulatedController::LoadDevices() {
|
|||
if (npad_id_type == NpadIdType::Player1 || npad_id_type == NpadIdType::Handheld) {
|
||||
camera_params[1] = Common::ParamPackage{"engine:camera,camera:1"};
|
||||
nfc_params[0] = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"};
|
||||
#ifndef ANDROID
|
||||
#ifndef __ANDROID__
|
||||
ring_params[1] = Common::ParamPackage{"engine:joycon,axis_x:100,axis_y:101"};
|
||||
#else
|
||||
android_params = Common::ParamPackage{"engine:android,port:100"};
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
#include "input_common/drivers/sdl_driver.h"
|
||||
#endif
|
||||
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
#include "input_common/drivers/android.h"
|
||||
#endif
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ struct InputSubsystem::Impl {
|
|||
RegisterEngine("cemuhookudp", udp_client);
|
||||
RegisterEngine("tas", tas_input);
|
||||
RegisterEngine("camera", camera);
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
RegisterEngine("android", android);
|
||||
#endif
|
||||
RegisterEngine("virtual_amiibo", virtual_amiibo);
|
||||
|
|
@ -119,7 +119,7 @@ struct InputSubsystem::Impl {
|
|||
UnregisterEngine(udp_client);
|
||||
UnregisterEngine(tas_input);
|
||||
UnregisterEngine(camera);
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
UnregisterEngine(android);
|
||||
#endif
|
||||
UnregisterEngine(virtual_amiibo);
|
||||
|
|
@ -138,13 +138,13 @@ struct InputSubsystem::Impl {
|
|||
Common::ParamPackage{{"display", "Any"}, {"engine", "any"}},
|
||||
};
|
||||
|
||||
#ifndef ANDROID
|
||||
#ifndef __ANDROID__
|
||||
auto keyboard_devices = keyboard->GetInputDevices();
|
||||
devices.insert(devices.end(), keyboard_devices.begin(), keyboard_devices.end());
|
||||
auto mouse_devices = mouse->GetInputDevices();
|
||||
devices.insert(devices.end(), mouse_devices.begin(), mouse_devices.end());
|
||||
#endif
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
auto android_devices = android->GetInputDevices();
|
||||
devices.insert(devices.end(), android_devices.begin(), android_devices.end());
|
||||
#endif
|
||||
|
|
@ -176,7 +176,7 @@ struct InputSubsystem::Impl {
|
|||
if (engine == mouse->GetEngineName()) {
|
||||
return mouse;
|
||||
}
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
if (engine == android->GetEngineName()) {
|
||||
return android;
|
||||
}
|
||||
|
|
@ -261,7 +261,7 @@ struct InputSubsystem::Impl {
|
|||
if (engine == mouse->GetEngineName()) {
|
||||
return true;
|
||||
}
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
if (engine == android->GetEngineName()) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -294,7 +294,7 @@ struct InputSubsystem::Impl {
|
|||
void BeginConfiguration() {
|
||||
keyboard->BeginConfiguration();
|
||||
mouse->BeginConfiguration();
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
android->BeginConfiguration();
|
||||
#endif
|
||||
#ifdef ENABLE_LIBUSB
|
||||
|
|
@ -310,7 +310,7 @@ struct InputSubsystem::Impl {
|
|||
void EndConfiguration() {
|
||||
keyboard->EndConfiguration();
|
||||
mouse->EndConfiguration();
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
android->EndConfiguration();
|
||||
#endif
|
||||
#ifdef ENABLE_LIBUSB
|
||||
|
|
@ -355,7 +355,7 @@ struct InputSubsystem::Impl {
|
|||
std::shared_ptr<Joycons> joycon;
|
||||
#endif
|
||||
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
std::shared_ptr<Android> android;
|
||||
#endif
|
||||
};
|
||||
|
|
@ -412,7 +412,7 @@ const Camera* InputSubsystem::GetCamera() const {
|
|||
return impl->camera.get();
|
||||
}
|
||||
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
Android* InputSubsystem::GetAndroid() {
|
||||
return impl->android.get();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef FRONTEND_H
|
||||
#define FRONTEND_H
|
||||
#pragma once
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include "qt_common/qt_common.h"
|
||||
|
|
@ -114,4 +113,3 @@ const QString GetTextInput(const QString& title = QString(), const QString& capt
|
|||
const QString& defaultText = QString());
|
||||
|
||||
} // namespace QtCommon::Frontend
|
||||
#endif // FRONTEND_H
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef QT_COMMON_H
|
||||
#define QT_COMMON_H
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <QWindow>
|
||||
|
|
@ -62,4 +61,3 @@ const QString tr(const std::string& str);
|
|||
|
||||
std::filesystem::path GetEdenCommand();
|
||||
} // namespace QtCommon
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef QT_APPLET_UTIL_H
|
||||
#define QT_APPLET_UTIL_H
|
||||
#pragma once
|
||||
|
||||
// TODO
|
||||
namespace QtCommon::Applets {}
|
||||
#endif // QT_APPLET_UTIL_H
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef QT_CONTENT_UTIL_H
|
||||
#define QT_CONTENT_UTIL_H
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include "common/common_types.h"
|
||||
|
|
@ -63,4 +62,3 @@ void configureFilesystemProvider(const std::string& filepath);
|
|||
// Profiles //
|
||||
void FixProfiles();
|
||||
} // namespace QtCommon::Content
|
||||
#endif // QT_CONTENT_UTIL_H
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include "common/common_types.h"
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace QtCommon::FS {
|
||||
|
||||
void LinkRyujinx(std::filesystem::path& from, std::filesystem::path& to);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef QT_GAME_UTIL_H
|
||||
#define QT_GAME_UTIL_H
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QStandardPaths>
|
||||
|
|
@ -78,5 +77,3 @@ void CreateHomeMenuShortcut(ShortcutTarget target);
|
|||
[[nodiscard]] bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image);
|
||||
|
||||
} // namespace QtCommon::Game
|
||||
|
||||
#endif // QT_GAME_UTIL_H
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef QT_META_H
|
||||
#define QT_META_H
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
|
|
@ -12,4 +11,3 @@ namespace QtCommon::Meta {
|
|||
void RegisterMetaTypes();
|
||||
|
||||
} // namespace QtCommon::Meta
|
||||
#endif // QT_META_H
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef QT_PATH_UTIL_H
|
||||
#define QT_PATH_UTIL_H
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include "common/common_types.h"
|
||||
|
|
@ -10,5 +9,3 @@
|
|||
namespace QtCommon::Path {
|
||||
bool OpenShaderCache(u64 program_id, QObject* parent);
|
||||
}
|
||||
|
||||
#endif // QT_PATH_UTIL_H
|
||||
|
|
|
|||
|
|
@ -1,16 +1,11 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef QT_ROM_UTIL_H
|
||||
#define QT_ROM_UTIL_H
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include "qt_common/qt_common.h"
|
||||
|
||||
namespace QtCommon::ROM {
|
||||
|
||||
bool RomFSRawCopy(size_t total_size, size_t& read_size, QtProgressCallback callback,
|
||||
const FileSys::VirtualDir& src, const FileSys::VirtualDir& dest, bool full);
|
||||
|
||||
bool RomFSRawCopy(size_t total_size, size_t& read_size, QtProgressCallback callback, const FileSys::VirtualDir& src, const FileSys::VirtualDir& dest, bool full);
|
||||
}
|
||||
#endif // QT_ROM_UTIL_H
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ Id EmitConvertU32U64(EmitContext& ctx, Id value) {
|
|||
}
|
||||
|
||||
Id EmitConvertF16F32(EmitContext& ctx, Id value) {
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
return ctx.OpFConvert(ctx.F16[1], value);
|
||||
#else
|
||||
const auto result = ctx.OpFConvert(ctx.F16[1], value);
|
||||
|
|
|
|||
|
|
@ -506,7 +506,7 @@ Id EmitImageSampleExplicitLod(EmitContext& ctx, IR::Inst* inst, const IR::Value&
|
|||
Id result = Emit(&EmitContext::OpImageSparseSampleExplicitLod,
|
||||
&EmitContext::OpImageSampleExplicitLod, ctx, inst, ctx.F32[4],
|
||||
Texture(ctx, info, index), coords, operands.Mask(), operands.Span());
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
if (Settings::values.fix_bloom_effects.GetValue()) {
|
||||
result = ctx.OpVectorTimesScalar(ctx.F32[4], result, ctx.Const(0.98f));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -801,7 +801,7 @@ void BufferCache<P>::UpdateVertexBufferSlot(u32 index, const Binding& binding) {
|
|||
template <class P>
|
||||
void BufferCache<P>::BindHostVertexBuffers() {
|
||||
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
const bool use_optimized_vertex_buffers = Settings::values.use_optimized_vertex_buffers.GetValue();
|
||||
#else
|
||||
constexpr bool use_optimized_vertex_buffers = true;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
|
||||
#include "video_core/gpu_logging/freedreno_debug.h"
|
||||
#include "common/logging.h"
|
||||
|
|
@ -49,4 +49,4 @@ std::string FreedrenoDebugger::GetBreadcrumbs() {
|
|||
|
||||
} // namespace GPU::Logging::Freedreno
|
||||
|
||||
#endif // ANDROID
|
||||
#endif // __ANDROID__
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
|
||||
#include <string>
|
||||
|
||||
|
|
@ -29,4 +29,4 @@ private:
|
|||
|
||||
} // namespace GPU::Logging::Freedreno
|
||||
|
||||
#endif // ANDROID
|
||||
#endif // __ANDROID__
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ namespace {
|
|||
constexpr AVPixelFormat PreferredGpuFormat = AV_PIX_FMT_NV12;
|
||||
constexpr AVPixelFormat PreferredCpuFormat = AV_PIX_FMT_YUV420P;
|
||||
constexpr std::array PreferredGpuDecoders = {
|
||||
#if defined (_WIN32)
|
||||
#if defined(_WIN32)
|
||||
AV_HWDEVICE_TYPE_CUDA,
|
||||
AV_HWDEVICE_TYPE_D3D11VA,
|
||||
AV_HWDEVICE_TYPE_DXVA2,
|
||||
|
|
@ -39,7 +39,7 @@ constexpr std::array PreferredGpuDecoders = {
|
|||
AV_HWDEVICE_TYPE_DRM,
|
||||
#elif defined(__APPLE__)
|
||||
AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
|
||||
#elif defined(ANDROID)
|
||||
#elif defined(__ANDROID__)
|
||||
AV_HWDEVICE_TYPE_MEDIACODEC,
|
||||
#elif defined(__unix__)
|
||||
AV_HWDEVICE_TYPE_CUDA,
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@
|
|||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
#include "video_core/gpu_logging/gpu_logging.h"
|
||||
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
#include "../../android/app/src/main/jni/android_settings.h"
|
||||
#endif
|
||||
|
||||
|
|
@ -328,7 +328,7 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> program
|
|||
size_t GetTotalPipelineWorkers() {
|
||||
const size_t max_core_threads =
|
||||
std::max<size_t>(static_cast<size_t>(std::thread::hardware_concurrency()), 2ULL) - 1ULL;
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
const int configured = AndroidSettings::values.pipeline_worker_count.GetValue();
|
||||
const int clamped = std::clamp(configured, 4, 8);
|
||||
const size_t desired = static_cast<size_t>(clamped);
|
||||
|
|
|
|||
|
|
@ -309,7 +309,7 @@ void PresentManager::CopyToSwapchain(Frame* frame) {
|
|||
try {
|
||||
// Recreate surface and swapchain if needed.
|
||||
if (requires_recreation) {
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
surface = CreateSurface(instance, render_window.GetWindowInfo());
|
||||
#endif
|
||||
RecreateSwapchain(frame);
|
||||
|
|
|
|||
|
|
@ -928,13 +928,13 @@ void RasterizerVulkan::LoadDiskResources(u64 title_id, std::stop_token stop_load
|
|||
}
|
||||
|
||||
void RasterizerVulkan::FlushWork() {
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
static constexpr u32 DRAWS_TO_DISPATCH = 512;
|
||||
static constexpr u32 CHECK_MASK = 3;
|
||||
#else
|
||||
static constexpr u32 DRAWS_TO_DISPATCH = 4096;
|
||||
static constexpr u32 CHECK_MASK = 7;
|
||||
#endif // ANDROID
|
||||
#endif // __ANDROID__
|
||||
|
||||
static_assert(DRAWS_TO_DISPATCH % (CHECK_MASK + 1) == 0);
|
||||
if ((++draw_counter & CHECK_MASK) != CHECK_MASK) {
|
||||
|
|
|
|||
|
|
@ -288,7 +288,7 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) {
|
|||
.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||
.queueFamilyIndexCount = 0,
|
||||
.pQueueFamilyIndices = nullptr,
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
// On Android, do not allow surface rotation to deviate from the frontend.
|
||||
.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR,
|
||||
#else
|
||||
|
|
@ -313,7 +313,7 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) {
|
|||
swapchain_ci.imageFormat, // Base format MUST be first
|
||||
VK_FORMAT_B8G8R8A8_UNORM,
|
||||
VK_FORMAT_B8G8R8A8_SRGB,
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
VK_FORMAT_R8G8B8A8_UNORM, // Android may use RGBA
|
||||
VK_FORMAT_R8G8B8A8_SRGB,
|
||||
#endif
|
||||
|
|
@ -338,7 +338,7 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities) {
|
|||
|
||||
images = swapchain.GetImages();
|
||||
image_count = static_cast<u32>(images.size());
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
// Android is already ordered the same as Switch.
|
||||
image_view_format = VK_FORMAT_R8G8B8A8_UNORM;
|
||||
#else
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#if defined(ANDROID) && defined(ARCHITECTURE_arm64)
|
||||
#if defined(__ANDROID__) && defined(ARCHITECTURE_arm64)
|
||||
#include <adrenotools/driver.h>
|
||||
#endif
|
||||
|
||||
|
|
@ -20,7 +20,7 @@ namespace Vulkan {
|
|||
using namespace Common::Literals;
|
||||
|
||||
TurboMode::TurboMode(const vk::Instance& instance, const vk::InstanceDispatch& dld)
|
||||
#ifndef ANDROID
|
||||
#ifndef __ANDROID__
|
||||
: m_device{CreateDevice(instance, dld, VK_NULL_HANDLE)}, m_allocator{m_device}
|
||||
#endif
|
||||
{
|
||||
|
|
@ -40,7 +40,7 @@ void TurboMode::QueueSubmitted() {
|
|||
}
|
||||
|
||||
void TurboMode::Run(std::stop_token stop_token) {
|
||||
#ifndef ANDROID
|
||||
#ifndef __ANDROID__
|
||||
auto& dld = m_device.GetLogical();
|
||||
|
||||
// Allocate buffer. 2MiB should be sufficient.
|
||||
|
|
@ -154,7 +154,7 @@ void TurboMode::Run(std::stop_token stop_token) {
|
|||
#endif
|
||||
|
||||
while (!stop_token.stop_requested()) {
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
#ifdef ARCHITECTURE_arm64
|
||||
adrenotools_set_turbo(true);
|
||||
#endif
|
||||
|
|
@ -232,7 +232,7 @@ void TurboMode::Run(std::stop_token stop_token) {
|
|||
std::chrono::milliseconds{100};
|
||||
});
|
||||
}
|
||||
#if defined(ANDROID) && defined(ARCHITECTURE_arm64)
|
||||
#if defined(__ANDROID__) && defined(ARCHITECTURE_arm64)
|
||||
adrenotools_set_turbo(false);
|
||||
#endif
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
|
@ -23,7 +26,7 @@ public:
|
|||
private:
|
||||
void Run(std::stop_token stop_token);
|
||||
|
||||
#ifndef ANDROID
|
||||
#ifndef __ANDROID__
|
||||
Device m_device;
|
||||
MemoryAllocator m_allocator;
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2013 Jorge Jimenez (jorge@iryoku.com)
|
||||
// SPDX-FileCopyrightText: 2013 Jose I. Echevarria (joseignacioechevarria@gmail.com)
|
||||
// SPDX-FileCopyrightText: 2013 Belen Masia (bmasia@unizar.es)
|
||||
|
|
@ -5,8 +8,7 @@
|
|||
// SPDX-FileCopyrightText: 2013 Diego Gutierrez (diegog@unizar.es)
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef AREATEX_H
|
||||
#define AREATEX_H
|
||||
#pragma once
|
||||
|
||||
#define AREATEX_WIDTH 160
|
||||
#define AREATEX_HEIGHT 560
|
||||
|
|
@ -11219,5 +11221,3 @@ static const unsigned char areaTexBytes[] = {
|
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2013 Jorge Jimenez (jorge@iryoku.com)
|
||||
// SPDX-FileCopyrightText: 2013 Jose I. Echevarria (joseignacioechevarria@gmail.com)
|
||||
// SPDX-FileCopyrightText: 2013 Belen Masia (bmasia@unizar.es)
|
||||
|
|
@ -5,8 +8,7 @@
|
|||
// SPDX-FileCopyrightText: 2013 Diego Gutierrez (diegog@unizar.es)
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef SEARCHTEX_H
|
||||
#define SEARCHTEX_H
|
||||
#pragma once
|
||||
|
||||
#define SEARCHTEX_WIDTH 64
|
||||
#define SEARCHTEX_HEIGHT 16
|
||||
|
|
@ -84,5 +86,3 @@ static const unsigned char searchTexBytes[] = {
|
|||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ VkBool32 DebugUtilCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity,
|
|||
[[maybe_unused]] void* user_data) {
|
||||
// Skip logging known false-positive validation errors
|
||||
switch (static_cast<u32>(data->messageIdNumber)) {
|
||||
#ifdef ANDROID
|
||||
#ifdef __ANDROID__
|
||||
case 0xbf9cf353u: // VUID-vkCmdBindVertexBuffers2-pBuffers-04111
|
||||
// The below are due to incorrect reporting of extendedDynamicState
|
||||
case 0x1093bebbu: // VUID-vkCmdSetCullMode-None-03384
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
#include "video_core/vulkan_common/vulkan_wrapper.h"
|
||||
#include "video_core/gpu_logging/gpu_logging.h"
|
||||
|
||||
#if defined(ANDROID) && defined(ARCHITECTURE_arm64)
|
||||
#if defined(__ANDROID__) && defined(ARCHITECTURE_arm64)
|
||||
#include <adrenotools/bcenabler.h>
|
||||
#include <android/api-level.h>
|
||||
#endif
|
||||
|
|
@ -294,7 +294,7 @@ ankerl::unordered_dense::map<VkFormat, VkFormatProperties> GetFormatProperties(v
|
|||
return format_properties;
|
||||
}
|
||||
|
||||
#if defined(ANDROID) && defined(ARCHITECTURE_arm64)
|
||||
#if defined(__ANDROID__) && defined(ARCHITECTURE_arm64)
|
||||
void OverrideBcnFormats(ankerl::unordered_dense::map<VkFormat, VkFormatProperties>& format_properties) {
|
||||
// These properties are extracted from Adreno driver 512.687.0
|
||||
constexpr VkFormatFeatureFlags tiling_features{VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT |
|
||||
|
|
@ -504,7 +504,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
|
|||
features.shader_atomic_int64.shaderSharedInt64Atomics = false;
|
||||
features.features.shaderInt64 = false;
|
||||
|
||||
#if defined(ANDROID) && defined(ARCHITECTURE_arm64)
|
||||
#if defined(__ANDROID__) && defined(ARCHITECTURE_arm64)
|
||||
// BCn patching only safe on Android 9+ (API 28+). Older versions crash on driver load.
|
||||
const auto major = (properties.properties.driverVersion >> 24) << 2;
|
||||
const auto minor = (properties.properties.driverVersion >> 12) & 0xFFFU;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ namespace Vulkan {
|
|||
std::shared_ptr<Common::DynamicLibrary> OpenLibrary(
|
||||
[[maybe_unused]] Core::Frontend::GraphicsContext* context) {
|
||||
LOG_DEBUG(Render_Vulkan, "Looking for a Vulkan library");
|
||||
#if defined(ANDROID) && defined(ARCHITECTURE_arm64)
|
||||
#if defined(__ANDROID__) && defined(ARCHITECTURE_arm64)
|
||||
// Android manages its Vulkan driver from the frontend.
|
||||
return context->GetDriverLibrary();
|
||||
#else
|
||||
|
|
|
|||
|
|
@ -256,18 +256,20 @@ namespace Vulkan {
|
|||
device.GetDispatchLoader());
|
||||
}
|
||||
|
||||
vk::Buffer
|
||||
MemoryAllocator::CreateBuffer(const VkBufferCreateInfo &ci, MemoryUsage usage) const
|
||||
{
|
||||
vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo &ci, MemoryUsage usage) const {
|
||||
// MESA will do memcpy() if not marked as host cached, so just force mark it for most buffers
|
||||
auto const anv_flags = (usage == MemoryUsage::Stream
|
||||
&& device.GetDriverID() == VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA)
|
||||
? VK_MEMORY_PROPERTY_HOST_CACHED_BIT : 0;
|
||||
const VmaAllocationCreateInfo alloc_ci = {
|
||||
.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage),
|
||||
.usage = MemoryUsageVma(usage),
|
||||
.requiredFlags = 0,
|
||||
.preferredFlags = MemoryUsagePreferredVmaFlags(usage),
|
||||
.memoryTypeBits = usage == MemoryUsage::Stream ? 0u : valid_memory_types,
|
||||
.pool = VK_NULL_HANDLE,
|
||||
.pUserData = nullptr,
|
||||
.priority = 0.f,
|
||||
.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage),
|
||||
.usage = MemoryUsageVma(usage),
|
||||
.requiredFlags = 0,
|
||||
.preferredFlags = MemoryUsagePreferredVmaFlags(usage) | anv_flags,
|
||||
.memoryTypeBits = usage == MemoryUsage::Stream ? 0u : valid_memory_types,
|
||||
.pool = VK_NULL_HANDLE,
|
||||
.pUserData = nullptr,
|
||||
.priority = 0.f,
|
||||
};
|
||||
|
||||
VkBuffer handle{};
|
||||
|
|
|
|||
|
|
@ -453,7 +453,7 @@ public:
|
|||
return handle != Type{};
|
||||
}
|
||||
|
||||
#ifndef ANDROID
|
||||
#ifndef __ANDROID__
|
||||
/**
|
||||
* Releases ownership of the managed handle.
|
||||
* The caller is responsible for managing the lifetime of the returned handle.
|
||||
|
|
@ -535,7 +535,7 @@ public:
|
|||
return handle != Type{};
|
||||
}
|
||||
|
||||
#ifndef ANDROID
|
||||
#ifndef __ANDROID__
|
||||
/**
|
||||
* Releases ownership of the managed handle.
|
||||
* The caller is responsible for managing the lifetime of the returned handle.
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef DATA_DIALOG_H
|
||||
#define DATA_DIALOG_H
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
#include "frontend_common/data_manager.h"
|
||||
|
|
@ -47,5 +46,3 @@ private:
|
|||
|
||||
std::optional<std::string> selectProfile();
|
||||
};
|
||||
|
||||
#endif // DATA_DIALOG_H
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef MIGRATION_DIALOG_H
|
||||
#define MIGRATION_DIALOG_H
|
||||
#pragma once
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QMessageBox>
|
||||
|
|
@ -29,5 +28,3 @@ private:
|
|||
|
||||
QAbstractButton* m_clickedButton;
|
||||
};
|
||||
|
||||
#endif // MIGRATION_DIALOG_H
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef MIGRATION_WORKER_H
|
||||
#define MIGRATION_WORKER_H
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include "common/fs/path_util.h"
|
||||
|
|
@ -73,5 +72,3 @@ private:
|
|||
MigrationStrategy strategy;
|
||||
QString success_text = tr("Data was migrated successfully.");
|
||||
};
|
||||
|
||||
#endif // MIGRATION_WORKER_H
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#ifndef RYUJINX_DIALOG_H
|
||||
#define RYUJINX_DIALOG_H
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <QDialog>
|
||||
|
|
@ -28,5 +27,3 @@ private:
|
|||
std::filesystem::path m_eden;
|
||||
std::filesystem::path m_ryu;
|
||||
};
|
||||
|
||||
#endif // RYUJINX_DIALOG_H
|
||||
|
|
|
|||
55
tools/cpp-lint.sh
Executable file
55
tools/cpp-lint.sh
Executable file
|
|
@ -0,0 +1,55 @@
|
|||
#!/bin/sh -ex
|
||||
|
||||
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# tools/../
|
||||
ROOTDIR=$(CDPATH='' cd -- "$(dirname -- "$0")/../" && pwd)
|
||||
BUILD_DIR="$ROOTDIR"/build
|
||||
SRC_DIR="$ROOTDIR"/src
|
||||
|
||||
die() {
|
||||
echo "-- $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $0 [command]
|
||||
|
||||
Dumb script that serves as a ad-hoc cpp-linter
|
||||
|
||||
Commands:
|
||||
once Check for #pragma once prescence in header files
|
||||
osdef Finds OS defines that are not recommended to use.
|
||||
inchk Check includes being valid/toolchain not being stupid
|
||||
EOF
|
||||
}
|
||||
|
||||
while :; do
|
||||
case "$1" in
|
||||
once)
|
||||
find "$SRC_DIR" -type f -name "*.h" -exec grep -L "#pragma once" {} +
|
||||
break
|
||||
;;
|
||||
osdef)
|
||||
# not recommended macros
|
||||
PATTERN="ANDROID\|_WIN64\|__linux\|__unix\|APPLE\|__APPLE"
|
||||
strings=("ANDROID" "_WIN64" "__linux" "__unix" "APPLE" "__APPLE" "linux" "unix")
|
||||
for item in "${strings[@]}"; do
|
||||
PATTERN="$PATTERN\|ifdef $item\|($item)"
|
||||
done
|
||||
# if statements for macros that shouldn't be if
|
||||
strings=("_WIN32" "_AIX" "__managarm__" "__unix__" "__linux__" "__FreeBSD__" "__NetBSD__" \
|
||||
"__OpenBSD__" "__DragonFly__" "__redox__" "__HAIKU__" "__OHOS__" "__FIREOS__")
|
||||
for item in "${strings[@]}"; do
|
||||
PATTERN="$PATTERN\|if $item"
|
||||
done
|
||||
find "$SRC_DIR" -type f -name "*.h" -exec grep -nw "$PATTERN" {} + || echo
|
||||
break
|
||||
;;
|
||||
*) usage ;;
|
||||
esac
|
||||
|
||||
shift
|
||||
done
|
||||
Loading…
Add table
Reference in a new issue