[common, core] remove uneeded memory indirection overhead at startup (#3306)

for core stuff:
just remove unique ptrs that dont need any pointer stability at all (afterall its an allocation within an allocation so yeah)

for fibers:
Main reasoning behind this is because virtualBuffer<> is stupidly fucking expensive and it also clutters my fstat view
ALSO mmap is a syscall, syscalls are bad for performance or whatever
ALSO std::vector<> is better suited for handling this kind of "fixed size thing where its like big but not THAT big" (512 KiB isn't going to kill your memory usage for each fiber...)

for core.cpp stuff
- inlines stuff into std::optional<> as opposed to std::unique_ptr<> (because yknow, we are making the Impl from an unique_ptr, allocating within an allocation is unnecessary)
- reorganizes the structures a bit so padding doesnt screw us up (it's not perfect but eh saves a measly 44 bytes)
- removes unused/dead code
- uses std::vector<> instead of std::deque<>

no perf impact expected, maybe some initialisation boost but very minimal impact nonethless
lto gets rid of most calls anyways - the heavy issue is with shared_ptr and the cache coherency from the atomics... but i clumped them together because well, they kinda do not suffer from cache coherency - hopefully not a mistake

this balloons the size of Impl to about 1.67 MB - which is fine because we throw it in the stack anyways

REST OF INTERFACES: most of them ballooned in size as well, but overhead is ok since its an allocation within an alloc, no stack is used (when it comes to storing these i mean)

Signed-off-by: lizzie lizzie@eden-emu.dev
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3306
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Co-authored-by: lizzie <lizzie@eden-emu.dev>
Co-committed-by: lizzie <lizzie@eden-emu.dev>
This commit is contained in:
lizzie 2026-01-16 23:39:16 +01:00 committed by crueter
parent 5768600c8b
commit 83a28dc251
No known key found for this signature in database
GPG key ID: 425ACD2D4830EBC6
40 changed files with 2602 additions and 2963 deletions

View file

@ -16,170 +16,160 @@ namespace Core {
using namespace Common::Literals;
class DynarmicCallbacks32 : public Dynarmic::A32::UserCallbacks {
public:
explicit DynarmicCallbacks32(ArmDynarmic32& parent, Kernel::KProcess* process)
: m_parent{parent}, m_memory(process->GetMemory()),
m_process(process), m_debugger_enabled{parent.m_system.DebuggerEnabled()},
m_check_memory_access{m_debugger_enabled ||
!Settings::values.cpuopt_ignore_memory_aborts.GetValue()} {}
DynarmicCallbacks32::DynarmicCallbacks32(ArmDynarmic32& parent, Kernel::KProcess* process)
: m_parent{parent}, m_memory(process->GetMemory())
, m_process(process), m_debugger_enabled{parent.m_system.DebuggerEnabled()}
, m_check_memory_access{m_debugger_enabled || !Settings::values.cpuopt_ignore_memory_aborts.GetValue()}
{}
u8 MemoryRead8(u32 vaddr) override {
CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Read);
return m_memory.Read8(vaddr);
}
u16 MemoryRead16(u32 vaddr) override {
CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Read);
return m_memory.Read16(vaddr);
}
u32 MemoryRead32(u32 vaddr) override {
CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Read);
return m_memory.Read32(vaddr);
}
u64 MemoryRead64(u32 vaddr) override {
CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Read);
return m_memory.Read64(vaddr);
}
std::optional<u32> MemoryReadCode(u32 vaddr) override {
if (!m_memory.IsValidVirtualAddressRange(vaddr, sizeof(u32))) {
return std::nullopt;
}
return m_memory.Read32(vaddr);
}
u8 DynarmicCallbacks32::MemoryRead8(u32 vaddr) {
CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Read);
return m_memory.Read8(vaddr);
}
u16 DynarmicCallbacks32::MemoryRead16(u32 vaddr) {
CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Read);
return m_memory.Read16(vaddr);
}
u32 DynarmicCallbacks32::MemoryRead32(u32 vaddr) {
CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Read);
return m_memory.Read32(vaddr);
}
u64 DynarmicCallbacks32::MemoryRead64(u32 vaddr) {
CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Read);
return m_memory.Read64(vaddr);
}
std::optional<u32> DynarmicCallbacks32::MemoryReadCode(u32 vaddr) {
if (!m_memory.IsValidVirtualAddressRange(vaddr, sizeof(u32)))
return std::nullopt;
return m_memory.Read32(vaddr);
}
void MemoryWrite8(u32 vaddr, u8 value) override {
if (CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write)) {
m_memory.Write8(vaddr, value);
}
void DynarmicCallbacks32::MemoryWrite8(u32 vaddr, u8 value) {
if (CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write)) {
m_memory.Write8(vaddr, value);
}
void MemoryWrite16(u32 vaddr, u16 value) override {
if (CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Write)) {
m_memory.Write16(vaddr, value);
}
}
void DynarmicCallbacks32::MemoryWrite16(u32 vaddr, u16 value) {
if (CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Write)) {
m_memory.Write16(vaddr, value);
}
void MemoryWrite32(u32 vaddr, u32 value) override {
if (CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Write)) {
m_memory.Write32(vaddr, value);
}
}
void DynarmicCallbacks32::MemoryWrite32(u32 vaddr, u32 value) {
if (CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Write)) {
m_memory.Write32(vaddr, value);
}
void MemoryWrite64(u32 vaddr, u64 value) override {
if (CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Write)) {
m_memory.Write64(vaddr, value);
}
}
void DynarmicCallbacks32::MemoryWrite64(u32 vaddr, u64 value) {
if (CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Write)) {
m_memory.Write64(vaddr, value);
}
}
bool MemoryWriteExclusive8(u32 vaddr, u8 value, u8 expected) override {
return CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write) &&
m_memory.WriteExclusive8(vaddr, value, expected);
}
bool MemoryWriteExclusive16(u32 vaddr, u16 value, u16 expected) override {
return CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Write) &&
m_memory.WriteExclusive16(vaddr, value, expected);
}
bool MemoryWriteExclusive32(u32 vaddr, u32 value, u32 expected) override {
return CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Write) &&
m_memory.WriteExclusive32(vaddr, value, expected);
}
bool MemoryWriteExclusive64(u32 vaddr, u64 value, u64 expected) override {
return CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Write) &&
m_memory.WriteExclusive64(vaddr, value, expected);
}
bool DynarmicCallbacks32::MemoryWriteExclusive8(u32 vaddr, u8 value, u8 expected) {
return CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write) &&
m_memory.WriteExclusive8(vaddr, value, expected);
}
bool DynarmicCallbacks32::MemoryWriteExclusive16(u32 vaddr, u16 value, u16 expected) {
return CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Write) &&
m_memory.WriteExclusive16(vaddr, value, expected);
}
bool DynarmicCallbacks32::MemoryWriteExclusive32(u32 vaddr, u32 value, u32 expected) {
return CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Write) &&
m_memory.WriteExclusive32(vaddr, value, expected);
}
bool DynarmicCallbacks32::MemoryWriteExclusive64(u32 vaddr, u64 value, u64 expected) {
return CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Write) &&
m_memory.WriteExclusive64(vaddr, value, expected);
}
void InterpreterFallback(u32 pc, std::size_t num_instructions) override {
m_parent.LogBacktrace(m_process);
LOG_ERROR(Core_ARM,
"Unimplemented instruction @ {:#X} for {} instructions (instr = {:08X})", pc,
num_instructions, m_memory.Read32(pc));
}
void DynarmicCallbacks32::InterpreterFallback(u32 pc, std::size_t num_instructions) {
m_parent.LogBacktrace(m_process);
LOG_ERROR(Core_ARM,
"Unimplemented instruction @ {:#X} for {} instructions (instr = {:08X})", pc,
num_instructions, m_memory.Read32(pc));
}
void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override {
switch (exception) {
case Dynarmic::A32::Exception::NoExecuteFault:
LOG_CRITICAL(Core_ARM, "Cannot execute instruction at unmapped address {:#08x}", pc);
ReturnException(pc, PrefetchAbort);
void DynarmicCallbacks32::ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) {
switch (exception) {
case Dynarmic::A32::Exception::NoExecuteFault:
LOG_CRITICAL(Core_ARM, "Cannot execute instruction at unmapped address {:#08x}", pc);
ReturnException(pc, PrefetchAbort);
return;
default:
if (m_debugger_enabled) {
ReturnException(pc, InstructionBreakpoint);
return;
default:
if (m_debugger_enabled) {
ReturnException(pc, InstructionBreakpoint);
return;
}
m_parent.LogBacktrace(m_process);
LOG_CRITICAL(Core_ARM,
"ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X}, thumb = {})",
exception, pc, m_memory.Read32(pc), m_parent.IsInThumbMode());
}
m_parent.LogBacktrace(m_process);
LOG_CRITICAL(Core_ARM,
"ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X}, thumb = {})",
exception, pc, m_memory.Read32(pc), m_parent.IsInThumbMode());
}
}
void CallSVC(u32 swi) override {
m_parent.m_svc_swi = swi;
m_parent.m_jit->HaltExecution(SupervisorCall);
}
void DynarmicCallbacks32::CallSVC(u32 swi) {
m_parent.m_svc_swi = swi;
m_parent.m_jit->HaltExecution(SupervisorCall);
}
void AddTicks(u64 ticks) override {
ASSERT_MSG(!m_parent.m_uses_wall_clock, "Dynarmic ticking disabled");
void DynarmicCallbacks32::AddTicks(u64 ticks) {
ASSERT_MSG(!m_parent.m_uses_wall_clock, "Dynarmic ticking disabled");
// Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a
// rough approximation of the amount of executed ticks in the system, it may be thrown off
// if not all cores are doing a similar amount of work. Instead of doing this, we should
// device a way so that timing is consistent across all cores without increasing the ticks 4
// times.
u64 amortized_ticks = ticks / Core::Hardware::NUM_CPU_CORES;
// Always execute at least one tick.
amortized_ticks = std::max<u64>(amortized_ticks, 1);
// Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a
// rough approximation of the amount of executed ticks in the system, it may be thrown off
// if not all cores are doing a similar amount of work. Instead of doing this, we should
// device a way so that timing is consistent across all cores without increasing the ticks 4
// times.
u64 amortized_ticks = ticks / Core::Hardware::NUM_CPU_CORES;
// Always execute at least one tick.
amortized_ticks = std::max<u64>(amortized_ticks, 1);
m_parent.m_system.CoreTiming().AddTicks(amortized_ticks);
}
m_parent.m_system.CoreTiming().AddTicks(amortized_ticks);
}
u64 GetTicksRemaining() override {
ASSERT_MSG(!m_parent.m_uses_wall_clock, "Dynarmic ticking disabled");
u64 DynarmicCallbacks32::GetTicksRemaining() {
ASSERT_MSG(!m_parent.m_uses_wall_clock, "Dynarmic ticking disabled");
return std::max<s64>(m_parent.m_system.CoreTiming().GetDowncount(), 0);
}
bool CheckMemoryAccess(u64 addr, u64 size, Kernel::DebugWatchpointType type) {
if (!m_check_memory_access) {
return true;
}
if (!m_memory.IsValidVirtualAddressRange(addr, size)) {
LOG_CRITICAL(Core_ARM, "Stopping execution due to unmapped memory access at {:#x}",
addr);
m_parent.m_jit->HaltExecution(PrefetchAbort);
return false;
}
if (!m_debugger_enabled) {
return true;
}
const auto match{m_parent.MatchingWatchpoint(addr, size, type)};
if (match) {
m_parent.m_halted_watchpoint = match;
m_parent.m_jit->HaltExecution(DataAbort);
return false;
}
return std::max<s64>(m_parent.m_system.CoreTiming().GetDowncount(), 0);
}
bool DynarmicCallbacks32::CheckMemoryAccess(u64 addr, u64 size, Kernel::DebugWatchpointType type) {
if (!m_check_memory_access) {
return true;
}
void ReturnException(u32 pc, Dynarmic::HaltReason hr) {
m_parent.GetContext(m_parent.m_breakpoint_context);
m_parent.m_breakpoint_context.pc = pc;
m_parent.m_breakpoint_context.r[15] = pc;
m_parent.m_jit->HaltExecution(hr);
if (!m_memory.IsValidVirtualAddressRange(addr, size)) {
LOG_CRITICAL(Core_ARM, "Stopping execution due to unmapped memory access at {:#x}",
addr);
m_parent.m_jit->HaltExecution(PrefetchAbort);
return false;
}
ArmDynarmic32& m_parent;
Core::Memory::Memory& m_memory;
Kernel::KProcess* m_process{};
const bool m_debugger_enabled{};
const bool m_check_memory_access{};
};
if (!m_debugger_enabled) {
return true;
}
std::shared_ptr<Dynarmic::A32::Jit> ArmDynarmic32::MakeJit(Common::PageTable* page_table) const {
const auto match{m_parent.MatchingWatchpoint(addr, size, type)};
if (match) {
m_parent.m_halted_watchpoint = match;
m_parent.m_jit->HaltExecution(DataAbort);
return false;
}
return true;
}
void DynarmicCallbacks32::ReturnException(u32 pc, Dynarmic::HaltReason hr) {
m_parent.GetContext(m_parent.m_breakpoint_context);
m_parent.m_breakpoint_context.pc = pc;
m_parent.m_breakpoint_context.r[15] = pc;
m_parent.m_jit->HaltExecution(hr);
}
void ArmDynarmic32::MakeJit(Common::PageTable* page_table) {
Dynarmic::A32::UserConfig config;
config.callbacks = m_cb.get();
config.callbacks = std::addressof(*m_cb);
config.coprocessors[15] = m_cp15;
config.define_unpredictable_behaviour = true;
@ -315,7 +305,7 @@ std::shared_ptr<Dynarmic::A32::Jit> ArmDynarmic32::MakeJit(Common::PageTable* pa
default:
break;
}
return std::make_unique<Dynarmic::A32::Jit>(config);
m_jit.emplace(config);
}
static std::pair<u32, u32> FpscrToFpsrFpcr(u32 fpscr) {
@ -360,21 +350,17 @@ u32 ArmDynarmic32::GetSvcNumber() const {
}
void ArmDynarmic32::GetSvcArguments(std::span<uint64_t, 8> args) const {
Dynarmic::A32::Jit& j = *m_jit;
Dynarmic::A32::Jit const& j = *m_jit;
auto& gpr = j.Regs();
for (size_t i = 0; i < 8; i++) {
for (size_t i = 0; i < 8; i++)
args[i] = gpr[i];
}
}
void ArmDynarmic32::SetSvcArguments(std::span<const uint64_t, 8> args) {
Dynarmic::A32::Jit& j = *m_jit;
auto& gpr = j.Regs();
for (size_t i = 0; i < 8; i++) {
gpr[i] = static_cast<u32>(args[i]);
}
for (size_t i = 0; i < 8; i++)
gpr[i] = u32(args[i]);
}
const Kernel::DebugWatchpoint* ArmDynarmic32::HaltedWatchpoint() const {
@ -387,11 +373,12 @@ void ArmDynarmic32::RewindBreakpointInstruction() {
ArmDynarmic32::ArmDynarmic32(System& system, bool uses_wall_clock, Kernel::KProcess* process,
DynarmicExclusiveMonitor& exclusive_monitor, std::size_t core_index)
: ArmInterface{uses_wall_clock}, m_system{system}, m_exclusive_monitor{exclusive_monitor},
m_cb(std::make_unique<DynarmicCallbacks32>(*this, process)),
m_cp15(std::make_shared<DynarmicCP15>(*this)), m_core_index{core_index} {
: ArmInterface{uses_wall_clock}, m_system{system}, m_exclusive_monitor{exclusive_monitor}
, m_cb(std::make_optional<DynarmicCallbacks32>(*this, process))
, m_cp15(std::make_shared<DynarmicCP15>(*this)), m_core_index{core_index}
{
auto& page_table_impl = process->GetPageTable().GetBasePageTable().GetImpl();
m_jit = MakeJit(&page_table_impl);
MakeJit(&page_table_impl);
}
ArmDynarmic32::~ArmDynarmic32() = default;
@ -401,23 +388,18 @@ void ArmDynarmic32::SetTpidrroEl0(u64 value) {
}
void ArmDynarmic32::GetContext(Kernel::Svc::ThreadContext& ctx) const {
Dynarmic::A32::Jit& j = *m_jit;
Dynarmic::A32::Jit const& j = *m_jit;
auto& gpr = j.Regs();
auto& fpr = j.ExtRegs();
for (size_t i = 0; i < 16; i++) {
for (size_t i = 0; i < 16; i++)
ctx.r[i] = gpr[i];
}
ctx.fp = gpr[11];
ctx.sp = gpr[13];
ctx.lr = gpr[14];
ctx.pc = gpr[15];
ctx.pstate = j.Cpsr();
static_assert(sizeof(fpr) <= sizeof(ctx.v));
std::memcpy(ctx.v.data(), &fpr, sizeof(fpr));
auto [fpsr, fpcr] = FpscrToFpsrFpcr(j.Fpscr());
ctx.fpcr = fpcr;
ctx.fpsr = fpsr;
@ -428,16 +410,11 @@ void ArmDynarmic32::SetContext(const Kernel::Svc::ThreadContext& ctx) {
Dynarmic::A32::Jit& j = *m_jit;
auto& gpr = j.Regs();
auto& fpr = j.ExtRegs();
for (size_t i = 0; i < 16; i++) {
gpr[i] = static_cast<u32>(ctx.r[i]);
}
for (size_t i = 0; i < 16; i++)
gpr[i] = u32(ctx.r[i]);
j.SetCpsr(ctx.pstate);
static_assert(sizeof(fpr) <= sizeof(ctx.v));
std::memcpy(&fpr, ctx.v.data(), sizeof(fpr));
j.SetFpscr(FpsrFpcrToFpscr(ctx.fpsr, ctx.fpcr));
m_cp15->uprw = static_cast<u32>(ctx.tpidr);
}

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -12,16 +15,50 @@ namespace Core::Memory {
class Memory;
}
namespace Kernel {
enum class DebugWatchpointType : u8;
class KPRocess;
}
namespace Core {
class DynarmicCallbacks32;
class ArmDynarmic32;
class DynarmicCP15;
class System;
class DynarmicCallbacks32 : public Dynarmic::A32::UserCallbacks {
public:
explicit DynarmicCallbacks32(ArmDynarmic32& parent, Kernel::KProcess* process);
u8 MemoryRead8(u32 vaddr) override;
u16 MemoryRead16(u32 vaddr) override;
u32 MemoryRead32(u32 vaddr) override;
u64 MemoryRead64(u32 vaddr) override;
std::optional<u32> MemoryReadCode(u32 vaddr) override;
void MemoryWrite8(u32 vaddr, u8 value) override;
void MemoryWrite16(u32 vaddr, u16 value) override;
void MemoryWrite32(u32 vaddr, u32 value) override;
void MemoryWrite64(u32 vaddr, u64 value) override;
bool MemoryWriteExclusive8(u32 vaddr, u8 value, u8 expected) override;
bool MemoryWriteExclusive16(u32 vaddr, u16 value, u16 expected) override;
bool MemoryWriteExclusive32(u32 vaddr, u32 value, u32 expected) override;
bool MemoryWriteExclusive64(u32 vaddr, u64 value, u64 expected) override;
void InterpreterFallback(u32 pc, std::size_t num_instructions) override;
void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override;
void CallSVC(u32 swi) override;
void AddTicks(u64 ticks) override;
u64 GetTicksRemaining() override;
bool CheckMemoryAccess(u64 addr, u64 size, Kernel::DebugWatchpointType type);
void ReturnException(u32 pc, Dynarmic::HaltReason hr);
ArmDynarmic32& m_parent;
Core::Memory::Memory& m_memory;
Kernel::KProcess* m_process{};
const bool m_debugger_enabled{};
const bool m_check_memory_access{};
};
class ArmDynarmic32 final : public ArmInterface {
public:
ArmDynarmic32(System& system, bool uses_wall_clock, Kernel::KProcess* process,
DynarmicExclusiveMonitor& exclusive_monitor, std::size_t core_index);
ArmDynarmic32(System& system, bool uses_wall_clock, Kernel::KProcess* process, DynarmicExclusiveMonitor& exclusive_monitor, std::size_t core_index);
~ArmDynarmic32() override;
Architecture GetArchitecture() const override {
@ -57,13 +94,13 @@ private:
friend class DynarmicCallbacks32;
friend class DynarmicCP15;
std::shared_ptr<Dynarmic::A32::Jit> MakeJit(Common::PageTable* page_table) const;
void MakeJit(Common::PageTable* page_table);
std::unique_ptr<DynarmicCallbacks32> m_cb{};
std::optional<DynarmicCallbacks32> m_cb{};
std::shared_ptr<DynarmicCP15> m_cp15{};
std::size_t m_core_index{};
std::shared_ptr<Dynarmic::A32::Jit> m_jit{};
std::optional<Dynarmic::A32::Jit> m_jit{};
// SVC callback
u32 m_svc_swi{};

View file

@ -13,223 +13,203 @@
namespace Core {
using Vector = Dynarmic::A64::Vector;
using namespace Common::Literals;
class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks {
public:
explicit DynarmicCallbacks64(ArmDynarmic64& parent, Kernel::KProcess* process)
: m_parent{parent}, m_memory(process->GetMemory()),
m_process(process), m_debugger_enabled{parent.m_system.DebuggerEnabled()},
m_check_memory_access{m_debugger_enabled ||
!Settings::values.cpuopt_ignore_memory_aborts.GetValue()} {}
DynarmicCallbacks64::DynarmicCallbacks64(ArmDynarmic64& parent, Kernel::KProcess* process)
: m_parent{parent}, m_memory(process->GetMemory())
, m_process(process), m_debugger_enabled{parent.m_system.DebuggerEnabled()}
, m_check_memory_access{m_debugger_enabled || !Settings::values.cpuopt_ignore_memory_aborts.GetValue()}
{}
u8 MemoryRead8(u64 vaddr) override {
CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Read);
return m_memory.Read8(vaddr);
}
u16 MemoryRead16(u64 vaddr) override {
CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Read);
return m_memory.Read16(vaddr);
}
u32 MemoryRead32(u64 vaddr) override {
CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Read);
return m_memory.Read32(vaddr);
}
u64 MemoryRead64(u64 vaddr) override {
CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Read);
return m_memory.Read64(vaddr);
}
Vector MemoryRead128(u64 vaddr) override {
CheckMemoryAccess(vaddr, 16, Kernel::DebugWatchpointType::Read);
return {m_memory.Read64(vaddr), m_memory.Read64(vaddr + 8)};
}
std::optional<u32> MemoryReadCode(u64 vaddr) override {
if (!m_memory.IsValidVirtualAddressRange(vaddr, sizeof(u32))) {
return std::nullopt;
}
return m_memory.Read32(vaddr);
}
u8 DynarmicCallbacks64::MemoryRead8(u64 vaddr) {
CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Read);
return m_memory.Read8(vaddr);
}
u16 DynarmicCallbacks64::MemoryRead16(u64 vaddr) {
CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Read);
return m_memory.Read16(vaddr);
}
u32 DynarmicCallbacks64::MemoryRead32(u64 vaddr) {
CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Read);
return m_memory.Read32(vaddr);
}
u64 DynarmicCallbacks64::MemoryRead64(u64 vaddr) {
CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Read);
return m_memory.Read64(vaddr);
}
Dynarmic::A64::Vector DynarmicCallbacks64::MemoryRead128(u64 vaddr) {
CheckMemoryAccess(vaddr, 16, Kernel::DebugWatchpointType::Read);
return {m_memory.Read64(vaddr), m_memory.Read64(vaddr + 8)};
}
std::optional<u32> DynarmicCallbacks64::MemoryReadCode(u64 vaddr) {
if (!m_memory.IsValidVirtualAddressRange(vaddr, sizeof(u32)))
return std::nullopt;
return m_memory.Read32(vaddr);
}
void MemoryWrite8(u64 vaddr, u8 value) override {
if (CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write)) {
m_memory.Write8(vaddr, value);
}
void DynarmicCallbacks64::MemoryWrite8(u64 vaddr, u8 value) {
if (CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write)) {
m_memory.Write8(vaddr, value);
}
void MemoryWrite16(u64 vaddr, u16 value) override {
if (CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Write)) {
m_memory.Write16(vaddr, value);
}
}
void DynarmicCallbacks64::MemoryWrite16(u64 vaddr, u16 value) {
if (CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Write)) {
m_memory.Write16(vaddr, value);
}
void MemoryWrite32(u64 vaddr, u32 value) override {
if (CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Write)) {
m_memory.Write32(vaddr, value);
}
}
void DynarmicCallbacks64::MemoryWrite32(u64 vaddr, u32 value) {
if (CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Write)) {
m_memory.Write32(vaddr, value);
}
void MemoryWrite64(u64 vaddr, u64 value) override {
if (CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Write)) {
m_memory.Write64(vaddr, value);
}
}
void DynarmicCallbacks64::MemoryWrite64(u64 vaddr, u64 value) {
if (CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Write)) {
m_memory.Write64(vaddr, value);
}
void MemoryWrite128(u64 vaddr, Vector value) override {
if (CheckMemoryAccess(vaddr, 16, Kernel::DebugWatchpointType::Write)) {
m_memory.Write64(vaddr, value[0]);
m_memory.Write64(vaddr + 8, value[1]);
}
}
void DynarmicCallbacks64::MemoryWrite128(u64 vaddr, Dynarmic::A64::Vector value) {
if (CheckMemoryAccess(vaddr, 16, Kernel::DebugWatchpointType::Write)) {
m_memory.Write64(vaddr, value[0]);
m_memory.Write64(vaddr + 8, value[1]);
}
}
bool MemoryWriteExclusive8(u64 vaddr, std::uint8_t value, std::uint8_t expected) override {
return CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write) &&
m_memory.WriteExclusive8(vaddr, value, expected);
}
bool MemoryWriteExclusive16(u64 vaddr, std::uint16_t value, std::uint16_t expected) override {
return CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Write) &&
m_memory.WriteExclusive16(vaddr, value, expected);
}
bool MemoryWriteExclusive32(u64 vaddr, std::uint32_t value, std::uint32_t expected) override {
return CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Write) &&
m_memory.WriteExclusive32(vaddr, value, expected);
}
bool MemoryWriteExclusive64(u64 vaddr, std::uint64_t value, std::uint64_t expected) override {
return CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Write) &&
m_memory.WriteExclusive64(vaddr, value, expected);
}
bool MemoryWriteExclusive128(u64 vaddr, Vector value, Vector expected) override {
return CheckMemoryAccess(vaddr, 16, Kernel::DebugWatchpointType::Write) &&
m_memory.WriteExclusive128(vaddr, value, expected);
}
bool DynarmicCallbacks64::MemoryWriteExclusive8(u64 vaddr, std::uint8_t value, std::uint8_t expected) {
return CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write) &&
m_memory.WriteExclusive8(vaddr, value, expected);
}
bool DynarmicCallbacks64::MemoryWriteExclusive16(u64 vaddr, std::uint16_t value, std::uint16_t expected) {
return CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Write) &&
m_memory.WriteExclusive16(vaddr, value, expected);
}
bool DynarmicCallbacks64::MemoryWriteExclusive32(u64 vaddr, std::uint32_t value, std::uint32_t expected) {
return CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Write) &&
m_memory.WriteExclusive32(vaddr, value, expected);
}
bool DynarmicCallbacks64::MemoryWriteExclusive64(u64 vaddr, std::uint64_t value, std::uint64_t expected) {
return CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Write) &&
m_memory.WriteExclusive64(vaddr, value, expected);
}
bool DynarmicCallbacks64::MemoryWriteExclusive128(u64 vaddr, Dynarmic::A64::Vector value, Dynarmic::A64::Vector expected) {
return CheckMemoryAccess(vaddr, 16, Kernel::DebugWatchpointType::Write) &&
m_memory.WriteExclusive128(vaddr, value, expected);
}
void InterpreterFallback(u64 pc, std::size_t num_instructions) override {
m_parent.LogBacktrace(m_process);
LOG_ERROR(Core_ARM,
"Unimplemented instruction @ {:#X} for {} instructions (instr = {:08X})", pc,
num_instructions, m_memory.Read32(pc));
void DynarmicCallbacks64::InterpreterFallback(u64 pc, std::size_t num_instructions) {
m_parent.LogBacktrace(m_process);
LOG_ERROR(Core_ARM, "Unimplemented instruction @ {:#X} for {} instructions (instr = {:08X})", pc,
num_instructions, m_memory.Read32(pc));
ReturnException(pc, PrefetchAbort);
}
void DynarmicCallbacks64::InstructionCacheOperationRaised(Dynarmic::A64::InstructionCacheOperation op, u64 value) {
switch (op) {
case Dynarmic::A64::InstructionCacheOperation::InvalidateByVAToPoU: {
static constexpr u64 ICACHE_LINE_SIZE = 64;
const u64 cache_line_start = value & ~(ICACHE_LINE_SIZE - 1);
m_parent.InvalidateCacheRange(cache_line_start, ICACHE_LINE_SIZE);
break;
}
case Dynarmic::A64::InstructionCacheOperation::InvalidateAllToPoU:
m_parent.ClearInstructionCache();
break;
case Dynarmic::A64::InstructionCacheOperation::InvalidateAllToPoUInnerSharable:
default:
LOG_DEBUG(Core_ARM, "Unprocesseed instruction cache operation: {}", op);
break;
}
m_parent.m_jit->HaltExecution(Dynarmic::HaltReason::CacheInvalidation);
}
void DynarmicCallbacks64::ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) {
switch (exception) {
case Dynarmic::A64::Exception::WaitForInterrupt:
case Dynarmic::A64::Exception::WaitForEvent:
case Dynarmic::A64::Exception::SendEvent:
case Dynarmic::A64::Exception::SendEventLocal:
case Dynarmic::A64::Exception::Yield:
LOG_TRACE(Core_ARM, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})", static_cast<std::size_t>(exception), pc, m_memory.Read32(pc));
return;
case Dynarmic::A64::Exception::NoExecuteFault:
LOG_CRITICAL(Core_ARM, "Cannot execute instruction at unmapped address {:#016x}", pc);
ReturnException(pc, PrefetchAbort);
}
void InstructionCacheOperationRaised(Dynarmic::A64::InstructionCacheOperation op,
u64 value) override {
switch (op) {
case Dynarmic::A64::InstructionCacheOperation::InvalidateByVAToPoU: {
static constexpr u64 ICACHE_LINE_SIZE = 64;
const u64 cache_line_start = value & ~(ICACHE_LINE_SIZE - 1);
m_parent.InvalidateCacheRange(cache_line_start, ICACHE_LINE_SIZE);
break;
}
case Dynarmic::A64::InstructionCacheOperation::InvalidateAllToPoU:
m_parent.ClearInstructionCache();
break;
case Dynarmic::A64::InstructionCacheOperation::InvalidateAllToPoUInnerSharable:
default:
LOG_DEBUG(Core_ARM, "Unprocesseed instruction cache operation: {}", op);
break;
}
m_parent.m_jit->HaltExecution(Dynarmic::HaltReason::CacheInvalidation);
}
void ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) override {
switch (exception) {
case Dynarmic::A64::Exception::WaitForInterrupt:
case Dynarmic::A64::Exception::WaitForEvent:
case Dynarmic::A64::Exception::SendEvent:
case Dynarmic::A64::Exception::SendEventLocal:
case Dynarmic::A64::Exception::Yield:
LOG_TRACE(Core_ARM, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})", static_cast<std::size_t>(exception), pc, m_memory.Read32(pc));
return;
case Dynarmic::A64::Exception::NoExecuteFault:
LOG_CRITICAL(Core_ARM, "Cannot execute instruction at unmapped address {:#016x}", pc);
ReturnException(pc, PrefetchAbort);
return;
default:
if (m_debugger_enabled) {
ReturnException(pc, InstructionBreakpoint);
} else {
m_parent.LogBacktrace(m_process);
LOG_CRITICAL(Core_ARM, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})", static_cast<std::size_t>(exception), pc, m_memory.Read32(pc));
}
return;
default:
if (m_debugger_enabled) {
ReturnException(pc, InstructionBreakpoint);
} else {
m_parent.LogBacktrace(m_process);
LOG_CRITICAL(Core_ARM, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})", static_cast<std::size_t>(exception), pc, m_memory.Read32(pc));
}
}
}
void CallSVC(u32 svc) override {
m_parent.m_svc = svc;
m_parent.m_jit->HaltExecution(SupervisorCall);
}
void DynarmicCallbacks64::CallSVC(u32 svc) {
m_parent.m_svc = svc;
m_parent.m_jit->HaltExecution(SupervisorCall);
}
void AddTicks(u64 ticks) override {
ASSERT_MSG(!m_parent.m_uses_wall_clock, "Dynarmic ticking disabled");
void DynarmicCallbacks64::AddTicks(u64 ticks) {
ASSERT_MSG(!m_parent.m_uses_wall_clock, "Dynarmic ticking disabled");
// Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a
// rough approximation of the amount of executed ticks in the system, it may be thrown off
// if not all cores are doing a similar amount of work. Instead of doing this, we should
// device a way so that timing is consistent across all cores without increasing the ticks 4
// times.
u64 amortized_ticks = ticks / Core::Hardware::NUM_CPU_CORES;
// Always execute at least one tick.
amortized_ticks = std::max<u64>(amortized_ticks, 1);
// Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a
// rough approximation of the amount of executed ticks in the system, it may be thrown off
// if not all cores are doing a similar amount of work. Instead of doing this, we should
// device a way so that timing is consistent across all cores without increasing the ticks 4
// times.
u64 amortized_ticks = ticks / Core::Hardware::NUM_CPU_CORES;
// Always execute at least one tick.
amortized_ticks = std::max<u64>(amortized_ticks, 1);
m_parent.m_system.CoreTiming().AddTicks(amortized_ticks);
}
m_parent.m_system.CoreTiming().AddTicks(amortized_ticks);
}
u64 GetTicksRemaining() override {
ASSERT_MSG(!m_parent.m_uses_wall_clock, "Dynarmic ticking disabled");
u64 DynarmicCallbacks64::GetTicksRemaining() {
ASSERT(!m_parent.m_uses_wall_clock && "Dynarmic ticking disabled");
return std::max<s64>(m_parent.m_system.CoreTiming().GetDowncount(), 0);
}
return std::max<s64>(m_parent.m_system.CoreTiming().GetDowncount(), 0);
}
u64 GetCNTPCT() override {
return m_parent.m_system.CoreTiming().GetClockTicks();
}
bool CheckMemoryAccess(u64 addr, u64 size, Kernel::DebugWatchpointType type) {
if (!m_check_memory_access) {
return true;
}
if (!m_memory.IsValidVirtualAddressRange(addr, size)) {
LOG_CRITICAL(Core_ARM, "Stopping execution due to unmapped memory access at {:#x}",
addr);
m_parent.m_jit->HaltExecution(PrefetchAbort);
return false;
}
if (!m_debugger_enabled) {
return true;
}
const auto match{m_parent.MatchingWatchpoint(addr, size, type)};
if (match) {
m_parent.m_halted_watchpoint = match;
m_parent.m_jit->HaltExecution(DataAbort);
return false;
}
u64 DynarmicCallbacks64::GetCNTPCT() {
return m_parent.m_system.CoreTiming().GetClockTicks();
}
bool DynarmicCallbacks64::CheckMemoryAccess(u64 addr, u64 size, Kernel::DebugWatchpointType type) {
if (!m_check_memory_access) {
return true;
}
void ReturnException(u64 pc, Dynarmic::HaltReason hr) {
m_parent.GetContext(m_parent.m_breakpoint_context);
m_parent.m_breakpoint_context.pc = pc;
m_parent.m_jit->HaltExecution(hr);
if (!m_memory.IsValidVirtualAddressRange(addr, size)) {
LOG_CRITICAL(Core_ARM, "Stopping execution due to unmapped memory access at {:#x}",
addr);
m_parent.m_jit->HaltExecution(PrefetchAbort);
return false;
}
ArmDynarmic64& m_parent;
Core::Memory::Memory& m_memory;
u64 m_tpidrro_el0{};
u64 m_tpidr_el0{};
Kernel::KProcess* m_process{};
const bool m_debugger_enabled{};
const bool m_check_memory_access{};
static constexpr u64 MinimumRunCycles = 10000U;
};
if (!m_debugger_enabled) {
return true;
}
std::shared_ptr<Dynarmic::A64::Jit> ArmDynarmic64::MakeJit(Common::PageTable* page_table,
std::size_t address_space_bits) const {
const auto match{m_parent.MatchingWatchpoint(addr, size, type)};
if (match) {
m_parent.m_halted_watchpoint = match;
m_parent.m_jit->HaltExecution(DataAbort);
return false;
}
return true;
}
void DynarmicCallbacks64::ReturnException(u64 pc, Dynarmic::HaltReason hr) {
m_parent.GetContext(m_parent.m_breakpoint_context);
m_parent.m_breakpoint_context.pc = pc;
m_parent.m_jit->HaltExecution(hr);
}
void ArmDynarmic64::MakeJit(Common::PageTable* page_table, std::size_t address_space_bits) {
Dynarmic::A64::UserConfig config;
// Callbacks
config.callbacks = m_cb.get();
config.callbacks = std::addressof(*m_cb);
// Memory
if (page_table) {
@ -375,7 +355,7 @@ std::shared_ptr<Dynarmic::A64::Jit> ArmDynarmic64::MakeJit(Common::PageTable* pa
default:
break;
}
return std::make_shared<Dynarmic::A64::Jit>(config);
m_jit.emplace(config);
}
HaltReason ArmDynarmic64::RunThread(Kernel::KThread* thread) {
@ -393,19 +373,15 @@ u32 ArmDynarmic64::GetSvcNumber() const {
}
void ArmDynarmic64::GetSvcArguments(std::span<uint64_t, 8> args) const {
Dynarmic::A64::Jit& j = *m_jit;
for (size_t i = 0; i < 8; i++) {
Dynarmic::A64::Jit const& j = *m_jit;
for (size_t i = 0; i < 8; i++)
args[i] = j.GetRegister(i);
}
}
void ArmDynarmic64::SetSvcArguments(std::span<const uint64_t, 8> args) {
Dynarmic::A64::Jit& j = *m_jit;
for (size_t i = 0; i < 8; i++) {
for (size_t i = 0; i < 8; i++)
j.SetRegister(i, args[i]);
}
}
const Kernel::DebugWatchpoint* ArmDynarmic64::HaltedWatchpoint() const {
@ -416,13 +392,14 @@ void ArmDynarmic64::RewindBreakpointInstruction() {
this->SetContext(m_breakpoint_context);
}
ArmDynarmic64::ArmDynarmic64(System& system, bool uses_wall_clock, Kernel::KProcess* process,
DynarmicExclusiveMonitor& exclusive_monitor, std::size_t core_index)
: ArmInterface{uses_wall_clock}, m_system{system}, m_exclusive_monitor{exclusive_monitor},
m_cb(std::make_unique<DynarmicCallbacks64>(*this, process)), m_core_index{core_index} {
ArmDynarmic64::ArmDynarmic64(System& system, bool uses_wall_clock, Kernel::KProcess* process, DynarmicExclusiveMonitor& exclusive_monitor, std::size_t core_index)
: ArmInterface{uses_wall_clock}, m_system{system}, m_exclusive_monitor{exclusive_monitor}
, m_cb(std::make_optional<DynarmicCallbacks64>(*this, process))
, m_core_index{core_index}
{
auto& page_table = process->GetPageTable().GetBasePageTable();
auto& page_table_impl = page_table.GetImpl();
m_jit = MakeJit(&page_table_impl, page_table.GetAddressSpaceWidth());
MakeJit(&page_table_impl, page_table.GetAddressSpaceWidth());
}
ArmDynarmic64::~ArmDynarmic64() = default;
@ -432,17 +409,14 @@ void ArmDynarmic64::SetTpidrroEl0(u64 value) {
}
void ArmDynarmic64::GetContext(Kernel::Svc::ThreadContext& ctx) const {
Dynarmic::A64::Jit& j = *m_jit;
Dynarmic::A64::Jit const& j = *m_jit;
auto gpr = j.GetRegisters();
auto fpr = j.GetVectors();
// TODO: this is inconvenient
for (size_t i = 0; i < 29; i++) {
for (size_t i = 0; i < 29; i++)
ctx.r[i] = gpr[i];
}
ctx.fp = gpr[29];
ctx.lr = gpr[30];
ctx.sp = j.GetSP();
ctx.pc = j.GetPC();
ctx.pstate = j.GetPstate();
@ -454,16 +428,12 @@ void ArmDynarmic64::GetContext(Kernel::Svc::ThreadContext& ctx) const {
void ArmDynarmic64::SetContext(const Kernel::Svc::ThreadContext& ctx) {
Dynarmic::A64::Jit& j = *m_jit;
// TODO: this is inconvenient
std::array<u64, 31> gpr;
for (size_t i = 0; i < 29; i++) {
for (size_t i = 0; i < 29; i++)
gpr[i] = ctx.r[i];
}
gpr[29] = ctx.fp;
gpr[30] = ctx.lr;
j.SetRegisters(gpr);
j.SetSP(ctx.sp);
j.SetPC(ctx.pc);

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -17,12 +20,57 @@ namespace Core::Memory {
class Memory;
}
namespace Kernel {
enum class DebugWatchpointType : u8;
class KPRocess;
}
namespace Core {
class DynarmicCallbacks64;
class ArmDynarmic64;
class DynarmicExclusiveMonitor;
class System;
class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks {
public:
explicit DynarmicCallbacks64(ArmDynarmic64& parent, Kernel::KProcess* process);
u8 MemoryRead8(u64 vaddr) override;
u16 MemoryRead16(u64 vaddr) override;
u32 MemoryRead32(u64 vaddr) override;
u64 MemoryRead64(u64 vaddr) override;
Dynarmic::A64::Vector MemoryRead128(u64 vaddr) override;
std::optional<u32> MemoryReadCode(u64 vaddr) override;
void MemoryWrite8(u64 vaddr, u8 value) override;
void MemoryWrite16(u64 vaddr, u16 value) override;
void MemoryWrite32(u64 vaddr, u32 value) override;
void MemoryWrite64(u64 vaddr, u64 value) override;
void MemoryWrite128(u64 vaddr, Dynarmic::A64::Vector value) override;
bool MemoryWriteExclusive8(u64 vaddr, std::uint8_t value, std::uint8_t expected) override;
bool MemoryWriteExclusive16(u64 vaddr, std::uint16_t value, std::uint16_t expected) override;
bool MemoryWriteExclusive32(u64 vaddr, std::uint32_t value, std::uint32_t expected) override;
bool MemoryWriteExclusive64(u64 vaddr, std::uint64_t value, std::uint64_t expected) override;
bool MemoryWriteExclusive128(u64 vaddr, Dynarmic::A64::Vector value, Dynarmic::A64::Vector expected) override;
void InterpreterFallback(u64 pc, std::size_t num_instructions) override;
void InstructionCacheOperationRaised(Dynarmic::A64::InstructionCacheOperation op, u64 value) override;
void ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) override;
void CallSVC(u32 svc) override;
void AddTicks(u64 ticks) override;
u64 GetTicksRemaining() override;
u64 GetCNTPCT() override;
bool CheckMemoryAccess(u64 addr, u64 size, Kernel::DebugWatchpointType type);
void ReturnException(u64 pc, Dynarmic::HaltReason hr);
ArmDynarmic64& m_parent;
Core::Memory::Memory& m_memory;
u64 m_tpidrro_el0{};
u64 m_tpidr_el0{};
Kernel::KProcess* m_process{};
const bool m_debugger_enabled{};
const bool m_check_memory_access{};
static constexpr u64 MinimumRunCycles = 10000U;
};
class ArmDynarmic64 final : public ArmInterface {
public:
ArmDynarmic64(System& system, bool uses_wall_clock, Kernel::KProcess* process,
@ -59,12 +107,11 @@ private:
private:
friend class DynarmicCallbacks64;
std::shared_ptr<Dynarmic::A64::Jit> MakeJit(Common::PageTable* page_table,
std::size_t address_space_bits) const;
std::unique_ptr<DynarmicCallbacks64> m_cb{};
void MakeJit(Common::PageTable* page_table, std::size_t address_space_bits);
std::optional<DynarmicCallbacks64> m_cb{};
std::size_t m_core_index{};
std::shared_ptr<Dynarmic::A64::Jit> m_jit{};
std::optional<Dynarmic::A64::Jit> m_jit{};
// SVC callback
u32 m_svc{};

View file

@ -112,11 +112,10 @@ struct System::Impl {
u64 program_id;
void Initialize(System& system) {
device_memory = std::make_unique<Core::DeviceMemory>();
device_memory.emplace();
is_multicore = Settings::values.use_multi_core.GetValue();
extended_memory_layout =
Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb;
extended_memory_layout = Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb;
core_timing.SetMulticore(is_multicore);
core_timing.Initialize([&system]() { system.RegisterHostThread(); });
@ -132,7 +131,7 @@ struct System::Impl {
// Create default implementations of applets if one is not provided.
frontend_applets.SetDefaultAppletsIfMissing();
is_async_gpu = Settings::values.use_asynchronous_gpu_emulation.GetValue();
auto const is_async_gpu = Settings::values.use_asynchronous_gpu_emulation.GetValue();
kernel.SetMulticore(is_multicore);
cpu_manager.SetMulticore(is_multicore);
@ -254,7 +253,7 @@ struct System::Impl {
}
void InitializeDebugger(System& system, u16 port) {
debugger = std::make_unique<Debugger>(system, port);
debugger.emplace(system, port);
}
void InitializeKernel(System& system) {
@ -268,24 +267,22 @@ struct System::Impl {
}
SystemResultStatus SetupForApplicationProcess(System& system, Frontend::EmuWindow& emu_window) {
host1x_core = std::make_unique<Tegra::Host1x::Host1x>(system);
host1x_core.emplace(system);
gpu_core = VideoCore::CreateGPU(emu_window, system);
if (!gpu_core) {
if (!gpu_core)
return SystemResultStatus::ErrorVideoCore;
}
audio_core = std::make_unique<AudioCore::AudioCore>(system);
audio_core.emplace(system);
service_manager = std::make_shared<Service::SM::ServiceManager>(kernel);
services =
std::make_unique<Service::Services>(service_manager, system, stop_event.get_token());
services.emplace(service_manager, system, stop_event.get_token());
is_powered_on = true;
exit_locked = false;
exit_requested = false;
if (Settings::values.enable_renderdoc_hotkey) {
renderdoc_api = std::make_unique<Tools::RenderdocAPI>();
renderdoc_api.emplace();
}
LOG_DEBUG(Core, "Initialized OK");
@ -303,16 +300,11 @@ struct System::Impl {
// Create the application process
Loader::ResultStatus load_result{};
std::vector<u8> control;
auto process =
Service::AM::CreateApplicationProcess(control, app_loader, load_result, system, file,
params.program_id, params.program_index);
auto process = Service::AM::CreateApplicationProcess(control, app_loader, load_result, system, file, params.program_id, params.program_index);
if (load_result != Loader::ResultStatus::Success) {
LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", load_result);
ShutdownMainProcess();
return static_cast<SystemResultStatus>(
static_cast<u32>(SystemResultStatus::ErrorLoader) + static_cast<u32>(load_result));
return SystemResultStatus(u32(SystemResultStatus::ErrorLoader) + u32(load_result));
}
if (!app_loader) {
@ -337,8 +329,7 @@ struct System::Impl {
// Set up the rest of the system.
SystemResultStatus init_result{SetupForApplicationProcess(system, emu_window)};
if (init_result != SystemResultStatus::Success) {
LOG_CRITICAL(Core, "Failed to initialize system (Error {})!",
static_cast<int>(init_result));
LOG_CRITICAL(Core, "Failed to initialize system (Error {})!", int(init_result));
ShutdownMainProcess();
return init_result;
}
@ -361,24 +352,19 @@ struct System::Impl {
}
}
perf_stats = std::make_unique<PerfStats>(params.program_id);
perf_stats.emplace(params.program_id);
// Reset counters and set time origin to current frame
GetAndResetPerfStats();
perf_stats->BeginSystemFrame();
std::string title_version;
const FileSys::PatchManager pm(params.program_id, system.GetFileSystemController(),
system.GetContentProvider());
const auto metadata = pm.GetControlMetadata();
if (metadata.first != nullptr) {
title_version = metadata.first->GetVersionString();
}
const FileSys::PatchManager pm(params.program_id, system.GetFileSystemController(), system.GetContentProvider());
auto const metadata = pm.GetControlMetadata();
std::string title_version = metadata.first != nullptr ? metadata.first->GetVersionString() : "";
if (app_loader->ReadProgramId(program_id) != Loader::ResultStatus::Success) {
LOG_ERROR(Core, "Failed to find program id for ROM");
}
GameSettings::LoadOverrides(program_id, gpu_core->Renderer());
if (auto room_member = Network::GetRoomMember().lock()) {
Network::GameInfo game_info;
@ -387,9 +373,7 @@ struct System::Impl {
game_info.version = title_version;
room_member->SendGameInfo(game_info);
}
status = SystemResultStatus::Success;
return status;
return SystemResultStatus::Success;
}
void ShutdownMainProcess() {
@ -448,112 +432,79 @@ struct System::Impl {
}
Loader::ResultStatus GetGameName(std::string& out) const {
if (app_loader == nullptr)
return Loader::ResultStatus::ErrorNotInitialized;
return app_loader->ReadTitle(out);
}
void SetStatus(SystemResultStatus new_status, const char* details = nullptr) {
status = new_status;
if (details) {
status_details = details;
}
return app_loader ? app_loader->ReadTitle(out) : Loader::ResultStatus::ErrorNotInitialized;
}
PerfStatsResults GetAndResetPerfStats() {
return perf_stats->GetAndResetStats(core_timing.GetGlobalTimeUs());
}
mutable std::mutex suspend_guard;
std::atomic_bool is_paused{};
std::atomic<bool> is_shutting_down{};
Timing::CoreTiming core_timing;
Kernel::KernelCore kernel;
/// RealVfsFilesystem instance
FileSys::VirtualFilesystem virtual_filesystem;
/// ContentProviderUnion instance
std::unique_ptr<FileSys::ContentProviderUnion> content_provider;
Service::FileSystem::FileSystemController fs_controller;
/// AppLoader used to load the current executing application
std::unique_ptr<Loader::AppLoader> app_loader;
std::unique_ptr<Tegra::GPU> gpu_core;
std::unique_ptr<Tegra::Host1x::Host1x> host1x_core;
std::unique_ptr<Core::DeviceMemory> device_memory;
std::unique_ptr<AudioCore::AudioCore> audio_core;
Core::HID::HIDCore hid_core;
CpuManager cpu_manager;
std::atomic_bool is_powered_on{};
bool exit_locked = false;
bool exit_requested = false;
bool nvdec_active{};
Reporter reporter;
std::unique_ptr<Memory::CheatEngine> cheat_engine;
std::unique_ptr<Tools::Freezer> memory_freezer;
std::array<u8, 0x20> build_id{};
std::unique_ptr<Tools::RenderdocAPI> renderdoc_api;
/// Applets
Service::AM::AppletManager applet_manager;
Service::AM::Frontend::FrontendAppletHolder frontend_applets;
/// APM (Performance) services
Service::APM::Controller apm_controller{core_timing};
/// Service State
Service::Glue::ARPManager arp_manager;
Service::Account::ProfileManager profile_manager;
/// Network instance
Network::NetworkInstance network_instance;
Core::SpeedLimiter speed_limiter;
ExecuteProgramCallback execute_program_callback;
ExitCallback exit_callback;
std::optional<Service::Services> services;
std::optional<Core::Debugger> debugger;
std::optional<Service::KernelHelpers::ServiceContext> general_channel_context;
std::optional<Service::Event> general_channel_event;
std::optional<Core::PerfStats> perf_stats;
std::optional<Tegra::Host1x::Host1x> host1x_core;
std::optional<Core::DeviceMemory> device_memory;
std::optional<AudioCore::AudioCore> audio_core;
std::optional<Memory::CheatEngine> cheat_engine;
std::optional<Tools::Freezer> memory_freezer;
std::optional<Tools::RenderdocAPI> renderdoc_api;
std::array<Core::GPUDirtyMemoryManager, Core::Hardware::NUM_CPU_CORES> gpu_dirty_memory_managers;
std::vector<std::vector<u8>> user_channel;
std::vector<std::vector<u8>> general_channel;
std::array<u64, Core::Hardware::NUM_CPU_CORES> dynarmic_ticks{};
std::array<u8, 0x20> build_id{};
/// Service manager
std::shared_ptr<Service::SM::ServiceManager> service_manager;
/// Services
std::unique_ptr<Service::Services> services;
/// Network instance
Network::NetworkInstance network_instance;
/// Debugger
std::unique_ptr<Core::Debugger> debugger;
SystemResultStatus status = SystemResultStatus::Success;
std::string status_details = "";
std::unique_ptr<Core::PerfStats> perf_stats;
Core::SpeedLimiter speed_limiter;
bool is_multicore{};
bool is_async_gpu{};
bool extended_memory_layout{};
ExecuteProgramCallback execute_program_callback;
ExitCallback exit_callback;
/// ContentProviderUnion instance
std::unique_ptr<FileSys::ContentProviderUnion> content_provider;
/// AppLoader used to load the current executing application
std::unique_ptr<Loader::AppLoader> app_loader;
std::unique_ptr<Tegra::GPU> gpu_core;
std::stop_source stop_event;
std::array<u64, Core::Hardware::NUM_CPU_CORES> dynarmic_ticks{};
std::array<Core::GPUDirtyMemoryManager, Core::Hardware::NUM_CPU_CORES>
gpu_dirty_memory_managers;
std::deque<std::vector<u8>> user_channel;
mutable std::mutex suspend_guard;
std::mutex general_channel_mutex;
std::deque<std::vector<u8>> general_channel;
std::unique_ptr<Service::KernelHelpers::ServiceContext> general_channel_context; // lazy
std::unique_ptr<Service::Event> general_channel_event; // lazy
bool general_channel_initialized{false};
std::atomic_bool is_paused{};
std::atomic_bool is_shutting_down{};
std::atomic_bool is_powered_on{};
bool is_multicore : 1 = false;
bool extended_memory_layout : 1 = false;
bool exit_locked : 1 = false;
bool exit_requested : 1 = false;
bool nvdec_active : 1 = false;
void EnsureGeneralChannelInitialized(System& system) {
if (general_channel_initialized) {
return;
if (!general_channel_event) {
general_channel_context.emplace(system, "GeneralChannel");
general_channel_event.emplace(*general_channel_context);
}
general_channel_context = std::make_unique<Service::KernelHelpers::ServiceContext>(system, "GeneralChannel");
general_channel_event = std::make_unique<Service::Event>(*general_channel_context);
general_channel_initialized = true;
}
};
@ -776,14 +727,6 @@ Loader::ResultStatus System::GetGameName(std::string& out) const {
return impl->GetGameName(out);
}
void System::SetStatus(SystemResultStatus new_status, const char* details) {
impl->SetStatus(new_status, details);
}
const std::string& System::GetStatusDetails() const {
return impl->status_details;
}
Loader::AppLoader& System::GetAppLoader() {
return *impl->app_loader;
}
@ -803,7 +746,7 @@ FileSys::VirtualFilesystem System::GetFilesystem() const {
void System::RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
const std::array<u8, 32>& build_id, u64 main_region_begin,
u64 main_region_size) {
impl->cheat_engine = std::make_unique<Memory::CheatEngine>(*this, list, build_id);
impl->cheat_engine.emplace(*this, list, build_id);
impl->cheat_engine->SetMainMemoryParameters(main_region_begin, main_region_size);
}
@ -964,11 +907,13 @@ void System::ExecuteProgram(std::size_t program_index) {
}
}
std::deque<std::vector<u8>>& System::GetUserChannel() {
/// @brief Gets a reference to the user channel stack.
/// It is used to transfer data between programs.
std::vector<std::vector<u8>>& System::GetUserChannel() {
return impl->user_channel;
}
std::deque<std::vector<u8>>& System::GetGeneralChannel() {
std::vector<std::vector<u8>>& System::GetGeneralChannel() {
return impl->general_channel;
}
@ -984,7 +929,7 @@ void System::PushGeneralChannelData(std::vector<u8>&& data) {
bool System::TryPopGeneralChannel(std::vector<u8>& out_data) {
std::scoped_lock lk{impl->general_channel_mutex};
if (!impl->general_channel_initialized || impl->general_channel.empty()) {
if (!impl->general_channel_event || impl->general_channel.empty()) {
return false;
}
out_data = std::move(impl->general_channel.back());

View file

@ -325,10 +325,6 @@ public:
/// Gets the name of the current game
[[nodiscard]] Loader::ResultStatus GetGameName(std::string& out) const;
void SetStatus(SystemResultStatus new_status, const char* details);
[[nodiscard]] const std::string& GetStatusDetails() const;
[[nodiscard]] Loader::AppLoader& GetAppLoader();
[[nodiscard]] const Loader::AppLoader& GetAppLoader() const;
@ -424,13 +420,8 @@ public:
*/
void ExecuteProgram(std::size_t program_index);
/**
* Gets a reference to the user channel stack.
* It is used to transfer data between programs.
*/
[[nodiscard]] std::deque<std::vector<u8>>& GetUserChannel();
[[nodiscard]] std::deque<std::vector<u8>>& GetGeneralChannel();
[[nodiscard]] std::vector<std::vector<u8>>& GetUserChannel();
[[nodiscard]] std::vector<std::vector<u8>>& GetGeneralChannel();
void PushGeneralChannelData(std::vector<u8>&& data);
bool TryPopGeneralChannel(std::vector<u8>& out_data);
[[nodiscard]] Service::Event& GetGeneralChannelEvent();

View file

@ -53,13 +53,6 @@ CoreTiming::~CoreTiming() {
Reset();
}
void CoreTiming::ThreadEntry(CoreTiming& instance) {
Common::SetCurrentThreadName("HostTiming");
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
instance.on_thread_init();
instance.ThreadLoop();
}
void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
Reset();
on_thread_init = std::move(on_thread_init_);
@ -67,7 +60,12 @@ void CoreTiming::Initialize(std::function<void()>&& on_thread_init_) {
shutting_down = false;
cpu_ticks = 0;
if (is_multicore) {
timer_thread = std::make_unique<std::jthread>(ThreadEntry, std::ref(*this));
timer_thread.emplace([](CoreTiming& instance) {
Common::SetCurrentThreadName("HostTiming");
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
instance.on_thread_init();
instance.ThreadLoop();
}, std::ref(*this));
}
}

View file

@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@ -140,8 +143,6 @@ public:
private:
struct Event;
static void ThreadEntry(CoreTiming& instance);
void ThreadLoop();
void Reset();
@ -164,7 +165,7 @@ private:
Common::Event pause_event{};
mutable std::mutex basic_lock;
std::mutex advance_lock;
std::unique_ptr<std::jthread> timer_thread;
std::optional<std::jthread> timer_thread;
std::atomic<bool> paused{};
std::atomic<bool> paused_set{};
std::atomic<bool> wait_set{};

View file

@ -1148,9 +1148,17 @@ Result KProcess::GetThreadList(s32* out_num_threads, KProcessAddress out_thread_
void KProcess::Switch(KProcess* cur_process, KProcess* next_process) {}
KProcess::KProcess(KernelCore& kernel)
: KAutoObjectWithSlabHeapAndContainer(kernel), m_page_table{kernel}, m_state_lock{kernel},
m_list_lock{kernel}, m_cond_var{kernel.System()}, m_address_arbiter{kernel.System()},
m_handle_table{kernel}, m_exclusive_monitor{}, m_memory{kernel.System()} {}
: KAutoObjectWithSlabHeapAndContainer(kernel)
, m_exclusive_monitor{}
, m_memory{kernel.System()}
, m_handle_table{kernel}
, m_page_table{kernel}
, m_state_lock{kernel}
, m_list_lock{kernel}
, m_cond_var{kernel.System()}
, m_address_arbiter{kernel.System()}
{}
KProcess::~KProcess() = default;
Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,

View file

@ -66,60 +66,55 @@ public:
private:
using SharedMemoryInfoList = Common::IntrusiveListBaseTraits<KSharedMemoryInfo>::ListType;
using TLPTree =
Common::IntrusiveRedBlackTreeBaseTraits<KThreadLocalPage>::TreeType<KThreadLocalPage>;
using TLPTree = Common::IntrusiveRedBlackTreeBaseTraits<KThreadLocalPage>::TreeType<KThreadLocalPage>;
using TLPIterator = TLPTree::iterator;
private:
KProcessPageTable m_page_table;
std::atomic<size_t> m_used_kernel_memory_size{};
TLPTree m_fully_used_tlp_tree{};
TLPTree m_partially_used_tlp_tree{};
s32 m_ideal_core_id{};
KResourceLimit* m_resource_limit{};
KSystemResource* m_system_resource{};
size_t m_memory_release_hint{};
State m_state{};
KLightLock m_state_lock;
KLightLock m_list_lock;
KConditionVariable m_cond_var;
KAddressArbiter m_address_arbiter;
std::array<u64, 4> m_entropy{};
bool m_is_signaled{};
bool m_is_initialized{};
u32 m_pointer_buffer_size = 0x8000; // Default pointer buffer size (can be game-specific later)
bool m_is_application{};
bool m_is_default_application_system_resource{};
bool m_is_hbl{};
std::array<char, 13> m_name{};
std::atomic<u16> m_num_running_threads{};
Svc::CreateProcessFlag m_flags{};
KMemoryManager::Pool m_memory_pool{};
s64 m_schedule_count{};
KCapabilities m_capabilities{};
u64 m_program_id{};
u64 m_process_id{};
KProcessAddress m_code_address{};
size_t m_code_size{};
size_t m_main_thread_stack_size{};
size_t m_max_process_memory{};
u32 m_version{};
KHandleTable m_handle_table;
KProcessAddress m_plr_address{};
KThread* m_exception_thread{};
ThreadList m_thread_list{};
SharedMemoryInfoList m_shared_memory_list{};
bool m_is_suspended{};
bool m_is_immortal{};
bool m_is_handle_table_initialized{};
std::array<std::unique_ptr<Core::ArmInterface>, Core::Hardware::NUM_CPU_CORES>
m_arm_interfaces{};
std::array<std::unique_ptr<Core::ArmInterface>, Core::Hardware::NUM_CPU_CORES> m_arm_interfaces{};
std::array<KThread*, Core::Hardware::NUM_CPU_CORES> m_running_threads{};
std::array<u64, Core::Hardware::NUM_CPU_CORES> m_running_thread_idle_counts{};
std::array<u64, Core::Hardware::NUM_CPU_CORES> m_running_thread_switch_counts{};
std::array<KThread*, Core::Hardware::NUM_CPU_CORES> m_pinned_threads{};
std::array<DebugWatchpoint, Core::Hardware::NUM_WATCHPOINTS> m_watchpoints{};
std::map<KProcessAddress, u64> m_debug_page_refcounts{};
#ifdef HAS_NCE
std::unordered_map<u64, u64> m_post_handlers{};
#endif
std::unique_ptr<Core::ExclusiveMonitor> m_exclusive_monitor;
Core::Memory::Memory m_memory;
KCapabilities m_capabilities{};
KProcessAddress m_code_address{};
KHandleTable m_handle_table;
KProcessAddress m_plr_address{};
ThreadList m_thread_list{};
SharedMemoryInfoList m_shared_memory_list{};
KProcessPageTable m_page_table;
std::atomic<size_t> m_used_kernel_memory_size{};
TLPTree m_fully_used_tlp_tree{};
TLPTree m_partially_used_tlp_tree{};
State m_state{};
KLightLock m_state_lock;
KLightLock m_list_lock;
KConditionVariable m_cond_var;
KAddressArbiter m_address_arbiter;
std::array<u64, 4> m_entropy{};
u32 m_pointer_buffer_size = 0x8000; // Default pointer buffer size (can be game-specific later)
std::array<char, 13> m_name{};
Svc::CreateProcessFlag m_flags{};
KMemoryManager::Pool m_memory_pool{};
KResourceLimit* m_resource_limit{};
KSystemResource* m_system_resource{};
KThread* m_exception_thread{};
size_t m_code_size{};
size_t m_main_thread_stack_size{};
size_t m_max_process_memory{};
size_t m_memory_release_hint{};
s64 m_schedule_count{};
u64 m_program_id{};
u64 m_process_id{};
std::atomic<s64> m_cpu_time{};
std::atomic<s64> m_num_process_switches{};
std::atomic<s64> m_num_thread_switches{};
@ -128,11 +123,20 @@ private:
std::atomic<s64> m_num_ipc_messages{};
std::atomic<s64> m_num_ipc_replies{};
std::atomic<s64> m_num_ipc_receives{};
#ifdef HAS_NCE
std::unordered_map<u64, u64> m_post_handlers{};
#endif
std::unique_ptr<Core::ExclusiveMonitor> m_exclusive_monitor;
Core::Memory::Memory m_memory;
s32 m_ideal_core_id{};
u32 m_version{};
std::atomic<u16> m_num_running_threads{};
bool m_is_signaled : 1 = false;
bool m_is_initialized : 1 = false;
bool m_is_application : 1 = false;
bool m_is_default_application_system_resource : 1 = false;
bool m_is_hbl : 1 = false;
bool m_is_suspended : 1 = false;
bool m_is_immortal : 1 = false;
bool m_is_handle_table_initialized : 1 = false;
private:
Result StartTermination();

View file

@ -88,11 +88,11 @@ struct KernelCore::Impl {
}
void Initialize(KernelCore& kernel) {
hardware_timer = std::make_unique<Kernel::KHardwareTimer>(kernel);
hardware_timer.emplace(kernel);
hardware_timer->Initialize();
global_object_list_container = std::make_unique<KAutoObjectWithListContainer>(kernel);
global_scheduler_context = std::make_unique<Kernel::GlobalSchedulerContext>(kernel);
global_object_list_container.emplace(kernel);
global_scheduler_context.emplace(kernel);
// Derive the initial memory layout from the emulated board
Init::InitializeSlabResourceCounts(kernel);
@ -212,10 +212,9 @@ struct KernelCore::Impl {
void InitializePhysicalCores() {
for (u32 i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) {
const s32 core{static_cast<s32>(i)};
schedulers[i] = std::make_unique<Kernel::KScheduler>(system.Kernel());
cores[i] = std::make_unique<Kernel::PhysicalCore>(system.Kernel(), i);
auto const core = s32(i);
schedulers[i].emplace(system.Kernel());
cores[i].emplace(system.Kernel(), i);
auto* main_thread{Kernel::KThread::Create(system.Kernel())};
main_thread->SetCurrentCore(core);
@ -280,57 +279,56 @@ struct KernelCore::Impl {
size -= rc_size;
// Initialize the resource managers' shared page manager.
resource_manager_page_manager = std::make_unique<KDynamicPageManager>();
resource_manager_page_manager.emplace();
resource_manager_page_manager->Initialize(address, size, std::max<size_t>(PageSize, KPageBufferSlabHeap::BufferSize));
// Initialize the KPageBuffer slab heap.
page_buffer_slab_heap.Initialize(system);
// Initialize the fixed-size slabheaps.
app_memory_block_heap = std::make_unique<KMemoryBlockSlabHeap>();
sys_memory_block_heap = std::make_unique<KMemoryBlockSlabHeap>();
block_info_heap = std::make_unique<KBlockInfoSlabHeap>();
app_memory_block_heap->Initialize(resource_manager_page_manager.get(), ApplicationMemoryBlockSlabHeapSize);
sys_memory_block_heap->Initialize(resource_manager_page_manager.get(), SystemMemoryBlockSlabHeapSize);
block_info_heap->Initialize(resource_manager_page_manager.get(), BlockInfoSlabHeapSize);
app_memory_block_heap.emplace();
sys_memory_block_heap.emplace();
block_info_heap.emplace();
app_memory_block_heap->Initialize(std::addressof(*resource_manager_page_manager), ApplicationMemoryBlockSlabHeapSize);
sys_memory_block_heap->Initialize(std::addressof(*resource_manager_page_manager), SystemMemoryBlockSlabHeapSize);
block_info_heap->Initialize(std::addressof(*resource_manager_page_manager), BlockInfoSlabHeapSize);
// Reserve all but a fixed number of remaining pages for the page table heap.
const size_t num_pt_pages = resource_manager_page_manager->GetCount() - resource_manager_page_manager->GetUsed() - ReservedDynamicPageCount;
page_table_heap = std::make_unique<KPageTableSlabHeap>();
page_table_heap.emplace();
// TODO(bunnei): Pass in address once we support kernel virtual memory allocations.
page_table_heap->Initialize(
resource_manager_page_manager.get(), num_pt_pages,
std::addressof(*resource_manager_page_manager), num_pt_pages,
/*GetPointer<KPageTableManager::RefCount>(address + size)*/ nullptr);
// Setup the slab managers.
KDynamicPageManager* const app_dynamic_page_manager = nullptr;
KDynamicPageManager* const sys_dynamic_page_manager =
/*KTargetSystem::IsDynamicResourceLimitsEnabled()*/ true
? resource_manager_page_manager.get()
: nullptr;
app_memory_block_manager = std::make_unique<KMemoryBlockSlabManager>();
sys_memory_block_manager = std::make_unique<KMemoryBlockSlabManager>();
app_block_info_manager = std::make_unique<KBlockInfoManager>();
sys_block_info_manager = std::make_unique<KBlockInfoManager>();
app_page_table_manager = std::make_unique<KPageTableManager>();
sys_page_table_manager = std::make_unique<KPageTableManager>();
? std::addressof(*resource_manager_page_manager) : nullptr;
app_memory_block_manager.emplace();
sys_memory_block_manager.emplace();
app_block_info_manager.emplace();
sys_block_info_manager.emplace();
app_page_table_manager.emplace();
sys_page_table_manager.emplace();
app_memory_block_manager->Initialize(app_dynamic_page_manager, app_memory_block_heap.get());
sys_memory_block_manager->Initialize(sys_dynamic_page_manager, sys_memory_block_heap.get());
app_memory_block_manager->Initialize(app_dynamic_page_manager, std::addressof(*app_memory_block_heap));
sys_memory_block_manager->Initialize(sys_dynamic_page_manager, std::addressof(*sys_memory_block_heap));
app_block_info_manager->Initialize(app_dynamic_page_manager, block_info_heap.get());
sys_block_info_manager->Initialize(sys_dynamic_page_manager, block_info_heap.get());
app_block_info_manager->Initialize(app_dynamic_page_manager, std::addressof(*block_info_heap));
sys_block_info_manager->Initialize(sys_dynamic_page_manager, std::addressof(*block_info_heap));
app_page_table_manager->Initialize(app_dynamic_page_manager, page_table_heap.get());
sys_page_table_manager->Initialize(sys_dynamic_page_manager, page_table_heap.get());
app_page_table_manager->Initialize(app_dynamic_page_manager, std::addressof(*page_table_heap));
sys_page_table_manager->Initialize(sys_dynamic_page_manager, std::addressof(*page_table_heap));
// Check that we have the correct number of dynamic pages available.
ASSERT(resource_manager_page_manager->GetCount() - resource_manager_page_manager->GetUsed() == ReservedDynamicPageCount);
// Create the system page table managers.
app_system_resource = std::make_unique<KSystemResource>(kernel);
sys_system_resource = std::make_unique<KSystemResource>(kernel);
app_system_resource.emplace(kernel);
sys_system_resource.emplace(kernel);
KAutoObject::Create(std::addressof(*app_system_resource));
KAutoObject::Create(std::addressof(*sys_system_resource));
@ -349,7 +347,7 @@ struct KernelCore::Impl {
}
void InitializeGlobalData(KernelCore& kernel) {
object_name_global_data = std::make_unique<KObjectNameGlobalData>(kernel);
object_name_global_data.emplace(kernel);
}
void MakeApplicationProcess(KProcess* process) {
@ -431,7 +429,7 @@ struct KernelCore::Impl {
}
void DeriveInitialMemoryLayout() {
memory_layout = std::make_unique<KMemoryLayout>();
memory_layout.emplace();
// Insert the root region for the virtual memory tree, from which all other regions will
// derive.
@ -726,7 +724,7 @@ struct KernelCore::Impl {
void InitializeMemoryLayout() {
// Initialize the memory manager.
memory_manager = std::make_unique<KMemoryManager>(system);
memory_manager.emplace(system);
const auto& management_region = memory_layout->GetPoolManagementRegion();
ASSERT(management_region.GetEndAddress() != 0);
memory_manager->Initialize(management_region.GetAddress(), management_region.GetSize());
@ -774,8 +772,8 @@ struct KernelCore::Impl {
std::mutex process_list_lock;
std::vector<KProcess*> process_list;
KProcess* application_process{};
std::unique_ptr<Kernel::GlobalSchedulerContext> global_scheduler_context;
std::unique_ptr<Kernel::KHardwareTimer> hardware_timer;
std::optional<Kernel::GlobalSchedulerContext> global_scheduler_context;
std::optional<Kernel::KHardwareTimer> hardware_timer;
Init::KSlabResourceCounts slab_resource_counts{};
KResourceLimit* system_resource_limit{};
@ -784,9 +782,9 @@ struct KernelCore::Impl {
std::shared_ptr<Core::Timing::EventType> preemption_event;
std::unique_ptr<KAutoObjectWithListContainer> global_object_list_container;
std::optional<KAutoObjectWithListContainer> global_object_list_container;
std::unique_ptr<KObjectNameGlobalData> object_name_global_data;
std::optional<KObjectNameGlobalData> object_name_global_data;
std::unordered_set<KAutoObject*> registered_objects;
std::unordered_set<KAutoObject*> registered_in_use_objects;
@ -794,28 +792,28 @@ struct KernelCore::Impl {
std::mutex server_lock;
std::vector<std::unique_ptr<Service::ServerManager>> server_managers;
std::array<std::unique_ptr<Kernel::PhysicalCore>, Core::Hardware::NUM_CPU_CORES> cores;
std::array<std::optional<Kernel::PhysicalCore>, Core::Hardware::NUM_CPU_CORES> cores;
// Next host thead ID to use, 0-3 IDs represent core threads, >3 represent others
std::atomic<u32> next_host_thread_id{Core::Hardware::NUM_CPU_CORES};
// Kernel memory management
std::unique_ptr<KMemoryManager> memory_manager;
std::optional<KMemoryManager> memory_manager;
// Resource managers
std::unique_ptr<KDynamicPageManager> resource_manager_page_manager;
std::unique_ptr<KPageTableSlabHeap> page_table_heap;
std::unique_ptr<KMemoryBlockSlabHeap> app_memory_block_heap;
std::unique_ptr<KMemoryBlockSlabHeap> sys_memory_block_heap;
std::unique_ptr<KBlockInfoSlabHeap> block_info_heap;
std::unique_ptr<KPageTableManager> app_page_table_manager;
std::unique_ptr<KPageTableManager> sys_page_table_manager;
std::unique_ptr<KMemoryBlockSlabManager> app_memory_block_manager;
std::unique_ptr<KMemoryBlockSlabManager> sys_memory_block_manager;
std::unique_ptr<KBlockInfoManager> app_block_info_manager;
std::unique_ptr<KBlockInfoManager> sys_block_info_manager;
std::unique_ptr<KSystemResource> app_system_resource;
std::unique_ptr<KSystemResource> sys_system_resource;
std::optional<KDynamicPageManager> resource_manager_page_manager;
std::optional<KPageTableSlabHeap> page_table_heap;
std::optional<KMemoryBlockSlabHeap> app_memory_block_heap;
std::optional<KMemoryBlockSlabHeap> sys_memory_block_heap;
std::optional<KBlockInfoSlabHeap> block_info_heap;
std::optional<KPageTableManager> app_page_table_manager;
std::optional<KPageTableManager> sys_page_table_manager;
std::optional<KMemoryBlockSlabManager> app_memory_block_manager;
std::optional<KMemoryBlockSlabManager> sys_memory_block_manager;
std::optional<KBlockInfoManager> app_block_info_manager;
std::optional<KBlockInfoManager> sys_block_info_manager;
std::optional<KSystemResource> app_system_resource;
std::optional<KSystemResource> sys_system_resource;
// Shared memory for services
Kernel::KSharedMemory* hid_shared_mem{};
@ -825,10 +823,10 @@ struct KernelCore::Impl {
Kernel::KSharedMemory* hidbus_shared_mem{};
// Memory layout
std::unique_ptr<KMemoryLayout> memory_layout;
std::optional<KMemoryLayout> memory_layout;
std::array<KThread*, Core::Hardware::NUM_CPU_CORES> shutdown_threads{};
std::array<std::unique_ptr<Kernel::KScheduler>, Core::Hardware::NUM_CPU_CORES> schedulers{};
std::array<std::optional<Kernel::KScheduler>, Core::Hardware::NUM_CPU_CORES> schedulers{};
bool is_multicore{};
std::atomic_bool is_shutting_down{};
@ -948,12 +946,9 @@ const Kernel::PhysicalCore& KernelCore::CurrentPhysicalCore() const {
}
Kernel::KScheduler* KernelCore::CurrentScheduler() {
const u32 core_id = impl->GetCurrentHostThreadID();
if (core_id >= Core::Hardware::NUM_CPU_CORES) {
// This is expected when called from not a guest thread
return {};
}
return impl->schedulers[core_id].get();
if (auto const core_id = impl->GetCurrentHostThreadID(); core_id < Core::Hardware::NUM_CPU_CORES)
return std::addressof(*impl->schedulers[core_id]);
return {}; // This is expected when called from not a guest thread
}
Kernel::KHardwareTimer& KernelCore::HardwareTimer() {

View file

@ -95,9 +95,9 @@ struct Applet {
bool request_exit_to_library_applet_at_execute_next_program_enabled{};
// Channels
std::deque<std::vector<u8>> user_channel_launch_parameter{};
std::deque<std::vector<u8>> preselected_user_launch_parameter{};
std::deque<std::vector<u8>> friend_invitation_storage_channel{};
std::vector<std::vector<u8>> user_channel_launch_parameter{};
std::vector<std::vector<u8>> preselected_user_launch_parameter{};
std::vector<std::vector<u8>> friend_invitation_storage_channel{};
// Context Stack
std::stack<SharedPointer<IStorage>> context_stack{};

View file

@ -7,6 +7,7 @@
#include <algorithm>
#include <cstring>
#include <vector>
#include <boost/container/static_vector.hpp>
#include "common/assert.h"
#include "common/common_types.h"
@ -40,96 +41,51 @@ constexpr u32 EXPECTED_MAGIC{0x36f81a1e}; // What we expect the encrypted bfttf
constexpr u64 SHARED_FONT_MEM_SIZE{0x1100000};
constexpr FontRegion EMPTY_REGION{0, 0};
static void DecryptSharedFont(const std::vector<u32>& input, Kernel::PhysicalMemory& output,
std::size_t& offset) {
ASSERT_MSG(offset + (input.size() * sizeof(u32)) < SHARED_FONT_MEM_SIZE,
"Shared fonts exceeds 17mb!");
ASSERT_MSG(input[0] == EXPECTED_MAGIC, "Failed to derive key, unexpected magic number");
static void DecryptSharedFont(const std::span<u32 const> input, std::span<u8> output, std::size_t& offset) {
ASSERT(offset + (input.size() * sizeof(u32)) < SHARED_FONT_MEM_SIZE && "Shared fonts exceeds 17mb!");
ASSERT(input[0] == EXPECTED_MAGIC && "Failed to derive key, unexpected magic number");
const u32 KEY = input[0] ^ EXPECTED_RESULT; // Derive key using an inverse xor
std::vector<u32> transformed_font(input.size());
// TODO(ogniK): Figure out a better way to do this
std::transform(input.begin(), input.end(), transformed_font.begin(),
[&KEY](u32 font_data) { return Common::swap32(font_data ^ KEY); });
std::transform(input.begin(), input.end(), transformed_font.begin(), [&KEY](u32 font_data) { return Common::swap32(font_data ^ KEY); });
transformed_font[1] = Common::swap32(transformed_font[1]) ^ KEY; // "re-encrypt" the size
std::memcpy(output.data() + offset, transformed_font.data(),
transformed_font.size() * sizeof(u32));
std::memcpy(output.data() + offset, transformed_font.data(), transformed_font.size() * sizeof(u32));
offset += transformed_font.size() * sizeof(u32);
}
void DecryptSharedFontToTTF(const std::vector<u32>& input, std::vector<u8>& output) {
ASSERT_MSG(input[0] == EXPECTED_MAGIC, "Failed to derive key, unexpected magic number");
if (input.size() < 2) {
LOG_ERROR(Service_NS, "Input font is empty");
return;
}
const u32 KEY = input[0] ^ EXPECTED_RESULT; // Derive key using an inverse xor
std::vector<u32> transformed_font(input.size());
// TODO(ogniK): Figure out a better way to do this
std::transform(input.begin(), input.end(), transformed_font.begin(),
[&KEY](u32 font_data) { return Common::swap32(font_data ^ KEY); });
std::memcpy(output.data(), transformed_font.data() + 2,
(transformed_font.size() - 2) * sizeof(u32));
std::transform(input.begin(), input.end(), transformed_font.begin(), [&KEY](u32 font_data) { return Common::swap32(font_data ^ KEY); });
std::memcpy(output.data(), transformed_font.data() + 2, (transformed_font.size() - 2) * sizeof(u32));
}
void EncryptSharedFont(const std::vector<u32>& input, std::vector<u8>& output,
std::size_t& offset) {
ASSERT_MSG(offset + (input.size() * sizeof(u32)) < SHARED_FONT_MEM_SIZE,
"Shared fonts exceeds 17mb!");
void EncryptSharedFont(const std::vector<u32>& input, std::vector<u8>& output, std::size_t& offset) {
ASSERT(offset + (input.size() * sizeof(u32)) < SHARED_FONT_MEM_SIZE && "Shared fonts exceeds 17mb!");
const auto key = Common::swap32(EXPECTED_RESULT ^ EXPECTED_MAGIC);
std::vector<u32> transformed_font(input.size() + 2);
transformed_font[0] = Common::swap32(EXPECTED_MAGIC);
transformed_font[1] = Common::swap32(static_cast<u32>(input.size() * sizeof(u32))) ^ key;
std::transform(input.begin(), input.end(), transformed_font.begin() + 2,
[key](u32 in) { return in ^ key; });
std::memcpy(output.data() + offset, transformed_font.data(),
transformed_font.size() * sizeof(u32));
std::transform(input.begin(), input.end(), transformed_font.begin() + 2, [key](u32 in) { return in ^ key; });
std::memcpy(output.data() + offset, transformed_font.data(), transformed_font.size() * sizeof(u32));
offset += transformed_font.size() * sizeof(u32);
}
// Helper function to make BuildSharedFontsRawRegions a bit nicer
static u32 GetU32Swapped(const u8* data) {
u32 value;
std::memcpy(&value, data, sizeof(value));
return Common::swap32(value);
}
struct IPlatformServiceManager::Impl {
const FontRegion& GetSharedFontRegion(std::size_t index) const {
if (index >= shared_font_regions.size() || shared_font_regions.empty()) {
// No font fallback
return EMPTY_REGION;
}
return shared_font_regions.at(index);
return index < shared_font_regions.size() ? shared_font_regions[index] : EMPTY_REGION;
}
void BuildSharedFontsRawRegions(const Kernel::PhysicalMemory& input) {
// As we can derive the xor key we can just populate the offsets
// based on the shared memory dump
unsigned cur_offset = 0;
for (std::size_t i = 0; i < SHARED_FONTS.size(); i++) {
// Out of shared fonts/invalid font
if (GetU32Swapped(input.data() + cur_offset) != EXPECTED_RESULT) {
break;
}
// Derive key within inverse xor
const u32 KEY = GetU32Swapped(input.data() + cur_offset) ^ EXPECTED_MAGIC;
const u32 SIZE = GetU32Swapped(input.data() + cur_offset + 4) ^ KEY;
shared_font_regions.push_back(FontRegion{cur_offset + 8, SIZE});
cur_offset += SIZE + 8;
}
}
/// Backing memory for the shared font data
std::shared_ptr<Kernel::PhysicalMemory> shared_font;
// Automatically populated based on shared_fonts dump or system archives.
std::vector<FontRegion> shared_font_regions;
// 6 builtin fonts + extra 2 for whatever may come after
boost::container::static_vector<FontRegion, 8> shared_font_regions;
/// Backing memory for the shared font data
std::array<u8, SHARED_FONT_MEM_SIZE> shared_font;
};
IPlatformServiceManager::IPlatformServiceManager(Core::System& system_, const char* service_name_)
@ -162,8 +118,6 @@ IPlatformServiceManager::IPlatformServiceManager(Core::System& system_, const ch
const auto* nand = fsc.GetSystemNANDContents();
std::size_t offset = 0;
// Rebuild shared fonts from data ncas or synthesize
impl->shared_font = std::make_shared<Kernel::PhysicalMemory>(SHARED_FONT_MEM_SIZE);
for (auto& font : SHARED_FONTS) {
FileSys::VirtualFile romfs;
const auto nca =
@ -197,9 +151,8 @@ IPlatformServiceManager::IPlatformServiceManager(Core::System& system_, const ch
std::transform(font_data_u32.begin(), font_data_u32.end(), font_data_u32.begin(),
Common::swap32);
// Font offset and size do not account for the header
const FontRegion region{static_cast<u32>(offset + 8),
static_cast<u32>((font_data_u32.size() * sizeof(u32)) - 8)};
DecryptSharedFont(font_data_u32, *impl->shared_font, offset);
const FontRegion region{u32(offset + 8), u32((font_data_u32.size() * sizeof(u32)) - 8)};
DecryptSharedFont(font_data_u32, impl->shared_font, offset);
impl->shared_font_regions.push_back(region);
}
}
@ -231,14 +184,12 @@ Result IPlatformServiceManager::GetSharedMemoryAddressOffset(Out<u32> out_shared
R_SUCCEED();
}
Result IPlatformServiceManager::GetSharedMemoryNativeHandle(
OutCopyHandle<Kernel::KSharedMemory> out_shared_memory_native_handle) {
Result IPlatformServiceManager::GetSharedMemoryNativeHandle(OutCopyHandle<Kernel::KSharedMemory> out_shared_memory_native_handle) {
// Map backing memory for the font data
LOG_DEBUG(Service_NS, "called");
// Create shared font memory object
std::memcpy(kernel.GetFontSharedMem().GetPointer(), impl->shared_font->data(),
impl->shared_font->size());
std::memcpy(kernel.GetFontSharedMem().GetPointer(), impl->shared_font.data(), impl->shared_font.size());
// FIXME: this shouldn't belong to the kernel
*out_shared_memory_native_handle = &kernel.GetFontSharedMem();