early-access version 2779
This commit is contained in:
parent
5dbf3888f4
commit
be59a9b258
14 changed files with 176 additions and 204 deletions
|
@ -1,7 +1,7 @@
|
||||||
yuzu emulator early access
|
yuzu emulator early access
|
||||||
=============
|
=============
|
||||||
|
|
||||||
This is the source code for early-access 2778.
|
This is the source code for early-access 2779.
|
||||||
|
|
||||||
## Legal Notice
|
## Legal Notice
|
||||||
|
|
||||||
|
|
|
@ -138,7 +138,6 @@ struct System::Impl {
|
||||||
|
|
||||||
kernel.Suspend(false);
|
kernel.Suspend(false);
|
||||||
core_timing.SyncPause(false);
|
core_timing.SyncPause(false);
|
||||||
cpu_manager.Pause(false);
|
|
||||||
is_paused = false;
|
is_paused = false;
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
|
@ -150,25 +149,22 @@ struct System::Impl {
|
||||||
|
|
||||||
core_timing.SyncPause(true);
|
core_timing.SyncPause(true);
|
||||||
kernel.Suspend(true);
|
kernel.Suspend(true);
|
||||||
cpu_manager.Pause(true);
|
|
||||||
is_paused = true;
|
is_paused = true;
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_lock<std::mutex> StallCPU() {
|
std::unique_lock<std::mutex> StallProcesses() {
|
||||||
std::unique_lock<std::mutex> lk(suspend_guard);
|
std::unique_lock<std::mutex> lk(suspend_guard);
|
||||||
kernel.Suspend(true);
|
kernel.Suspend(true);
|
||||||
core_timing.SyncPause(true);
|
core_timing.SyncPause(true);
|
||||||
cpu_manager.Pause(true);
|
|
||||||
return lk;
|
return lk;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnstallCPU() {
|
void UnstallProcesses() {
|
||||||
if (!is_paused) {
|
if (!is_paused) {
|
||||||
core_timing.SyncPause(false);
|
core_timing.SyncPause(false);
|
||||||
kernel.Suspend(false);
|
kernel.Suspend(false);
|
||||||
cpu_manager.Pause(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -334,6 +330,8 @@ struct System::Impl {
|
||||||
gpu_core->NotifyShutdown();
|
gpu_core->NotifyShutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kernel.ShutdownCores();
|
||||||
|
cpu_manager.Shutdown();
|
||||||
debugger.reset();
|
debugger.reset();
|
||||||
services.reset();
|
services.reset();
|
||||||
service_manager.reset();
|
service_manager.reset();
|
||||||
|
@ -499,12 +497,12 @@ void System::DetachDebugger() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_lock<std::mutex> System::StallCPU() {
|
std::unique_lock<std::mutex> System::StallProcesses() {
|
||||||
return impl->StallCPU();
|
return impl->StallProcesses();
|
||||||
}
|
}
|
||||||
|
|
||||||
void System::UnstallCPU() {
|
void System::UnstallProcesses() {
|
||||||
impl->UnstallCPU();
|
impl->UnstallProcesses();
|
||||||
}
|
}
|
||||||
|
|
||||||
void System::InitializeDebugger() {
|
void System::InitializeDebugger() {
|
||||||
|
|
|
@ -163,8 +163,8 @@ public:
|
||||||
/// Forcibly detach the debugger if it is running.
|
/// Forcibly detach the debugger if it is running.
|
||||||
void DetachDebugger();
|
void DetachDebugger();
|
||||||
|
|
||||||
std::unique_lock<std::mutex> StallCPU();
|
std::unique_lock<std::mutex> StallProcesses();
|
||||||
void UnstallCPU();
|
void UnstallProcesses();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the debugger.
|
* Initialize the debugger.
|
||||||
|
|
|
@ -16,31 +16,28 @@
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
CpuManager::CpuManager(System& system_)
|
CpuManager::CpuManager(System& system_) : system{system_} {}
|
||||||
: pause_barrier{std::make_unique<Common::Barrier>(1)}, system{system_} {}
|
|
||||||
CpuManager::~CpuManager() = default;
|
CpuManager::~CpuManager() = default;
|
||||||
|
|
||||||
void CpuManager::ThreadStart(std::stop_token stop_token, CpuManager& cpu_manager,
|
void CpuManager::ThreadStart(std::stop_token stop_token, CpuManager& cpu_manager,
|
||||||
std::size_t core) {
|
std::size_t core) {
|
||||||
cpu_manager.RunThread(stop_token, core);
|
cpu_manager.RunThread(core);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CpuManager::Initialize() {
|
void CpuManager::Initialize() {
|
||||||
running_mode = true;
|
num_cores = is_multicore ? Core::Hardware::NUM_CPU_CORES : 1;
|
||||||
if (is_multicore) {
|
|
||||||
for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
|
for (std::size_t core = 0; core < num_cores; core++) {
|
||||||
core_data[core].host_thread = std::jthread(ThreadStart, std::ref(*this), core);
|
core_data[core].host_thread = std::jthread(ThreadStart, std::ref(*this), core);
|
||||||
}
|
}
|
||||||
pause_barrier = std::make_unique<Common::Barrier>(Core::Hardware::NUM_CPU_CORES + 1);
|
|
||||||
} else {
|
|
||||||
core_data[0].host_thread = std::jthread(ThreadStart, std::ref(*this), 0);
|
|
||||||
pause_barrier = std::make_unique<Common::Barrier>(2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CpuManager::Shutdown() {
|
void CpuManager::Shutdown() {
|
||||||
running_mode = false;
|
for (std::size_t core = 0; core < num_cores; core++) {
|
||||||
Pause(false);
|
if (core_data[core].host_thread.joinable()) {
|
||||||
|
core_data[core].host_thread.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::function<void(void*)> CpuManager::GetGuestThreadStartFunc() {
|
std::function<void(void*)> CpuManager::GetGuestThreadStartFunc() {
|
||||||
|
@ -51,8 +48,8 @@ std::function<void(void*)> CpuManager::GetIdleThreadStartFunc() {
|
||||||
return IdleThreadFunction;
|
return IdleThreadFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::function<void(void*)> CpuManager::GetSuspendThreadStartFunc() {
|
std::function<void(void*)> CpuManager::GetShutdownThreadStartFunc() {
|
||||||
return SuspendThreadFunction;
|
return ShutdownThreadFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CpuManager::GuestThreadFunction(void* cpu_manager_) {
|
void CpuManager::GuestThreadFunction(void* cpu_manager_) {
|
||||||
|
@ -82,17 +79,12 @@ void CpuManager::IdleThreadFunction(void* cpu_manager_) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CpuManager::SuspendThreadFunction(void* cpu_manager_) {
|
void CpuManager::ShutdownThreadFunction(void* cpu_manager) {
|
||||||
CpuManager* cpu_manager = static_cast<CpuManager*>(cpu_manager_);
|
static_cast<CpuManager*>(cpu_manager)->ShutdownThread();
|
||||||
if (cpu_manager->is_multicore) {
|
|
||||||
cpu_manager->MultiCoreRunSuspendThread();
|
|
||||||
} else {
|
|
||||||
cpu_manager->SingleCoreRunSuspendThread();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void* CpuManager::GetStartFuncParamater() {
|
void* CpuManager::GetStartFuncParameter() {
|
||||||
return static_cast<void*>(this);
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -134,21 +126,6 @@ void CpuManager::MultiCoreRunIdleThread() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CpuManager::MultiCoreRunSuspendThread() {
|
|
||||||
auto& kernel = system.Kernel();
|
|
||||||
kernel.CurrentScheduler()->OnThreadStart();
|
|
||||||
while (true) {
|
|
||||||
auto core = kernel.CurrentPhysicalCoreIndex();
|
|
||||||
auto& scheduler = *kernel.CurrentScheduler();
|
|
||||||
Kernel::KThread* current_thread = scheduler.GetCurrentThread();
|
|
||||||
current_thread->DisableDispatch();
|
|
||||||
|
|
||||||
Common::Fiber::YieldTo(current_thread->GetHostContext(), *core_data[core].host_context);
|
|
||||||
ASSERT(core == kernel.CurrentPhysicalCoreIndex());
|
|
||||||
scheduler.RescheduleCurrentCore();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
/// SingleCore ///
|
/// SingleCore ///
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -194,21 +171,6 @@ void CpuManager::SingleCoreRunIdleThread() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CpuManager::SingleCoreRunSuspendThread() {
|
|
||||||
auto& kernel = system.Kernel();
|
|
||||||
kernel.CurrentScheduler()->OnThreadStart();
|
|
||||||
while (true) {
|
|
||||||
auto core = kernel.GetCurrentHostThreadID();
|
|
||||||
auto& scheduler = *kernel.CurrentScheduler();
|
|
||||||
Kernel::KThread* current_thread = scheduler.GetCurrentThread();
|
|
||||||
current_thread->DisableDispatch();
|
|
||||||
|
|
||||||
Common::Fiber::YieldTo(current_thread->GetHostContext(), *core_data[0].host_context);
|
|
||||||
ASSERT(core == kernel.GetCurrentHostThreadID());
|
|
||||||
scheduler.RescheduleCurrentCore();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void CpuManager::PreemptSingleCore(bool from_running_enviroment) {
|
void CpuManager::PreemptSingleCore(bool from_running_enviroment) {
|
||||||
{
|
{
|
||||||
auto& kernel = system.Kernel();
|
auto& kernel = system.Kernel();
|
||||||
|
@ -241,24 +203,16 @@ void CpuManager::PreemptSingleCore(bool from_running_enviroment) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CpuManager::Pause(bool paused) {
|
void CpuManager::ShutdownThread() {
|
||||||
std::scoped_lock lk{pause_lock};
|
auto& kernel = system.Kernel();
|
||||||
|
auto core = is_multicore ? kernel.CurrentPhysicalCoreIndex() : 0;
|
||||||
|
auto* current_thread = kernel.GetCurrentEmuThread();
|
||||||
|
|
||||||
if (pause_state == paused) {
|
Common::Fiber::YieldTo(current_thread->GetHostContext(), *core_data[core].host_context);
|
||||||
return;
|
UNREACHABLE();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the new state
|
void CpuManager::RunThread(std::size_t core) {
|
||||||
pause_state.store(paused);
|
|
||||||
|
|
||||||
// Wake up any waiting threads
|
|
||||||
pause_state.notify_all();
|
|
||||||
|
|
||||||
// Wait for all threads to successfully change state before returning
|
|
||||||
pause_barrier->Sync();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CpuManager::RunThread(std::stop_token stop_token, std::size_t core) {
|
|
||||||
/// Initialization
|
/// Initialization
|
||||||
system.RegisterCoreThread(core);
|
system.RegisterCoreThread(core);
|
||||||
std::string name;
|
std::string name;
|
||||||
|
@ -272,8 +226,6 @@ void CpuManager::RunThread(std::stop_token stop_token, std::size_t core) {
|
||||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
|
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
|
||||||
auto& data = core_data[core];
|
auto& data = core_data[core];
|
||||||
data.host_context = Common::Fiber::ThreadToFiber();
|
data.host_context = Common::Fiber::ThreadToFiber();
|
||||||
const bool sc_sync = !is_async_gpu && !is_multicore;
|
|
||||||
bool sc_sync_first_use = sc_sync;
|
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
SCOPE_EXIT({
|
SCOPE_EXIT({
|
||||||
|
@ -281,32 +233,13 @@ void CpuManager::RunThread(std::stop_token stop_token, std::size_t core) {
|
||||||
MicroProfileOnThreadExit();
|
MicroProfileOnThreadExit();
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Running
|
// Running
|
||||||
while (running_mode) {
|
if (!is_async_gpu && !is_multicore) {
|
||||||
if (pause_state.load(std::memory_order_relaxed)) {
|
|
||||||
// Wait for caller to acknowledge pausing
|
|
||||||
pause_barrier->Sync();
|
|
||||||
|
|
||||||
// Wait until unpaused
|
|
||||||
pause_state.wait(true, std::memory_order_relaxed);
|
|
||||||
|
|
||||||
// Wait for caller to acknowledge unpausing
|
|
||||||
pause_barrier->Sync();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sc_sync_first_use) {
|
|
||||||
system.GPU().ObtainContext();
|
system.GPU().ObtainContext();
|
||||||
sc_sync_first_use = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Emulation was stopped
|
|
||||||
if (stop_token.stop_requested()) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto current_thread = system.Kernel().CurrentScheduler()->GetCurrentThread();
|
auto current_thread = system.Kernel().CurrentScheduler()->GetCurrentThread();
|
||||||
Common::Fiber::YieldTo(data.host_context, *current_thread->GetHostContext());
|
Common::Fiber::YieldTo(data.host_context, *current_thread->GetHostContext());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|
|
@ -46,12 +46,10 @@ public:
|
||||||
void Initialize();
|
void Initialize();
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
|
||||||
void Pause(bool paused);
|
|
||||||
|
|
||||||
static std::function<void(void*)> GetGuestThreadStartFunc();
|
static std::function<void(void*)> GetGuestThreadStartFunc();
|
||||||
static std::function<void(void*)> GetIdleThreadStartFunc();
|
static std::function<void(void*)> GetIdleThreadStartFunc();
|
||||||
static std::function<void(void*)> GetSuspendThreadStartFunc();
|
static std::function<void(void*)> GetShutdownThreadStartFunc();
|
||||||
void* GetStartFuncParamater();
|
void* GetStartFuncParameter();
|
||||||
|
|
||||||
void PreemptSingleCore(bool from_running_enviroment = true);
|
void PreemptSingleCore(bool from_running_enviroment = true);
|
||||||
|
|
||||||
|
@ -63,38 +61,33 @@ private:
|
||||||
static void GuestThreadFunction(void* cpu_manager);
|
static void GuestThreadFunction(void* cpu_manager);
|
||||||
static void GuestRewindFunction(void* cpu_manager);
|
static void GuestRewindFunction(void* cpu_manager);
|
||||||
static void IdleThreadFunction(void* cpu_manager);
|
static void IdleThreadFunction(void* cpu_manager);
|
||||||
static void SuspendThreadFunction(void* cpu_manager);
|
static void ShutdownThreadFunction(void* cpu_manager);
|
||||||
|
|
||||||
void MultiCoreRunGuestThread();
|
void MultiCoreRunGuestThread();
|
||||||
void MultiCoreRunGuestLoop();
|
void MultiCoreRunGuestLoop();
|
||||||
void MultiCoreRunIdleThread();
|
void MultiCoreRunIdleThread();
|
||||||
void MultiCoreRunSuspendThread();
|
|
||||||
|
|
||||||
void SingleCoreRunGuestThread();
|
void SingleCoreRunGuestThread();
|
||||||
void SingleCoreRunGuestLoop();
|
void SingleCoreRunGuestLoop();
|
||||||
void SingleCoreRunIdleThread();
|
void SingleCoreRunIdleThread();
|
||||||
void SingleCoreRunSuspendThread();
|
|
||||||
|
|
||||||
static void ThreadStart(std::stop_token stop_token, CpuManager& cpu_manager, std::size_t core);
|
static void ThreadStart(std::stop_token stop_token, CpuManager& cpu_manager, std::size_t core);
|
||||||
|
|
||||||
void RunThread(std::stop_token stop_token, std::size_t core);
|
void ShutdownThread();
|
||||||
|
void RunThread(std::size_t core);
|
||||||
|
|
||||||
struct CoreData {
|
struct CoreData {
|
||||||
std::shared_ptr<Common::Fiber> host_context;
|
std::shared_ptr<Common::Fiber> host_context;
|
||||||
std::jthread host_thread;
|
std::jthread host_thread;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::atomic<bool> running_mode{};
|
|
||||||
std::atomic<bool> pause_state{};
|
|
||||||
std::unique_ptr<Common::Barrier> pause_barrier{};
|
|
||||||
std::mutex pause_lock{};
|
|
||||||
|
|
||||||
std::array<CoreData, Core::Hardware::NUM_CPU_CORES> core_data{};
|
std::array<CoreData, Core::Hardware::NUM_CPU_CORES> core_data{};
|
||||||
|
|
||||||
bool is_async_gpu{};
|
bool is_async_gpu{};
|
||||||
bool is_multicore{};
|
bool is_multicore{};
|
||||||
std::atomic<std::size_t> current_core{};
|
std::atomic<std::size_t> current_core{};
|
||||||
std::size_t idle_count{};
|
std::size_t idle_count{};
|
||||||
|
std::size_t num_cores{};
|
||||||
static constexpr std::size_t max_cycle_runs = 5;
|
static constexpr std::size_t max_cycle_runs = 5;
|
||||||
|
|
||||||
System& system;
|
System& system;
|
||||||
|
|
|
@ -141,9 +141,6 @@ private:
|
||||||
AsyncReceiveInto(signal_pipe, pipe_data, [&](auto d) { PipeData(d); });
|
AsyncReceiveInto(signal_pipe, pipe_data, [&](auto d) { PipeData(d); });
|
||||||
AsyncReceiveInto(client_socket, client_data, [&](auto d) { ClientData(d); });
|
AsyncReceiveInto(client_socket, client_data, [&](auto d) { ClientData(d); });
|
||||||
|
|
||||||
// Stop the emulated CPU.
|
|
||||||
AllCoreStop();
|
|
||||||
|
|
||||||
// Set the active thread.
|
// Set the active thread.
|
||||||
UpdateActiveThread();
|
UpdateActiveThread();
|
||||||
|
|
||||||
|
@ -159,7 +156,7 @@ private:
|
||||||
switch (info.type) {
|
switch (info.type) {
|
||||||
case SignalType::Stopped:
|
case SignalType::Stopped:
|
||||||
// Stop emulation.
|
// Stop emulation.
|
||||||
AllCoreStop();
|
PauseEmulation();
|
||||||
|
|
||||||
// Notify the client.
|
// Notify the client.
|
||||||
active_thread = info.thread;
|
active_thread = info.thread;
|
||||||
|
@ -171,7 +168,6 @@ private:
|
||||||
frontend->ShuttingDown();
|
frontend->ShuttingDown();
|
||||||
|
|
||||||
// Wait for emulation to shut down gracefully now.
|
// Wait for emulation to shut down gracefully now.
|
||||||
suspend.reset();
|
|
||||||
signal_pipe.close();
|
signal_pipe.close();
|
||||||
client_socket.shutdown(boost::asio::socket_base::shutdown_both);
|
client_socket.shutdown(boost::asio::socket_base::shutdown_both);
|
||||||
LOG_INFO(Debug_GDBStub, "Shut down server");
|
LOG_INFO(Debug_GDBStub, "Shut down server");
|
||||||
|
@ -189,32 +185,24 @@ private:
|
||||||
std::scoped_lock lk{connection_lock};
|
std::scoped_lock lk{connection_lock};
|
||||||
stopped = true;
|
stopped = true;
|
||||||
}
|
}
|
||||||
AllCoreStop();
|
PauseEmulation();
|
||||||
UpdateActiveThread();
|
UpdateActiveThread();
|
||||||
frontend->Stopped(active_thread);
|
frontend->Stopped(active_thread);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DebuggerAction::Continue:
|
case DebuggerAction::Continue:
|
||||||
active_thread->SetStepState(Kernel::StepState::NotStepping);
|
ResumeEmulation();
|
||||||
ResumeInactiveThreads();
|
|
||||||
AllCoreResume();
|
|
||||||
break;
|
break;
|
||||||
case DebuggerAction::StepThreadUnlocked:
|
case DebuggerAction::StepThreadUnlocked:
|
||||||
active_thread->SetStepState(Kernel::StepState::StepPending);
|
active_thread->SetStepState(Kernel::StepState::StepPending);
|
||||||
ResumeInactiveThreads();
|
active_thread->Resume(Kernel::SuspendType::Debug);
|
||||||
AllCoreResume();
|
ResumeEmulation(active_thread);
|
||||||
break;
|
break;
|
||||||
case DebuggerAction::StepThreadLocked:
|
case DebuggerAction::StepThreadLocked:
|
||||||
active_thread->SetStepState(Kernel::StepState::StepPending);
|
active_thread->SetStepState(Kernel::StepState::StepPending);
|
||||||
SuspendInactiveThreads();
|
active_thread->Resume(Kernel::SuspendType::Debug);
|
||||||
AllCoreResume();
|
|
||||||
break;
|
break;
|
||||||
case DebuggerAction::ShutdownEmulation: {
|
case DebuggerAction::ShutdownEmulation: {
|
||||||
// Suspend all threads and release any locks held
|
|
||||||
active_thread->RequestSuspend(Kernel::SuspendType::Debug);
|
|
||||||
SuspendInactiveThreads();
|
|
||||||
AllCoreResume();
|
|
||||||
|
|
||||||
// Spawn another thread that will exit after shutdown,
|
// Spawn another thread that will exit after shutdown,
|
||||||
// to avoid a deadlock
|
// to avoid a deadlock
|
||||||
Core::System* system_ref{&system};
|
Core::System* system_ref{&system};
|
||||||
|
@ -226,42 +214,33 @@ private:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AllCoreStop() {
|
void PauseEmulation() {
|
||||||
if (!suspend) {
|
// Put all threads to sleep on next scheduler round.
|
||||||
suspend = system.StallCPU();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AllCoreResume() {
|
|
||||||
stopped = false;
|
|
||||||
system.UnstallCPU();
|
|
||||||
suspend.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SuspendInactiveThreads() {
|
|
||||||
for (auto* thread : ThreadList()) {
|
for (auto* thread : ThreadList()) {
|
||||||
if (thread != active_thread) {
|
|
||||||
thread->RequestSuspend(Kernel::SuspendType::Debug);
|
thread->RequestSuspend(Kernel::SuspendType::Debug);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// Signal an interrupt so that scheduler will fire.
|
||||||
|
system.Kernel().InterruptAllPhysicalCores();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ResumeInactiveThreads() {
|
void ResumeEmulation(Kernel::KThread* except = nullptr) {
|
||||||
|
// Wake up all threads.
|
||||||
for (auto* thread : ThreadList()) {
|
for (auto* thread : ThreadList()) {
|
||||||
if (thread != active_thread) {
|
if (thread == except) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
thread->Resume(Kernel::SuspendType::Debug);
|
thread->Resume(Kernel::SuspendType::Debug);
|
||||||
thread->SetStepState(Kernel::StepState::NotStepping);
|
thread->SetStepState(Kernel::StepState::NotStepping);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void UpdateActiveThread() {
|
void UpdateActiveThread() {
|
||||||
const auto& threads{ThreadList()};
|
const auto& threads{ThreadList()};
|
||||||
if (std::find(threads.begin(), threads.end(), active_thread) == threads.end()) {
|
if (std::find(threads.begin(), threads.end(), active_thread) == threads.end()) {
|
||||||
active_thread = threads[0];
|
active_thread = threads[0];
|
||||||
}
|
}
|
||||||
active_thread->Resume(Kernel::SuspendType::Debug);
|
|
||||||
active_thread->SetStepState(Kernel::StepState::NotStepping);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<Kernel::KThread*>& ThreadList() {
|
const std::vector<Kernel::KThread*>& ThreadList() {
|
||||||
|
@ -277,7 +256,6 @@ private:
|
||||||
boost::asio::io_context io_context;
|
boost::asio::io_context io_context;
|
||||||
boost::process::async_pipe signal_pipe;
|
boost::process::async_pipe signal_pipe;
|
||||||
boost::asio::ip::tcp::socket client_socket;
|
boost::asio::ip::tcp::socket client_socket;
|
||||||
std::optional<std::unique_lock<std::mutex>> suspend;
|
|
||||||
|
|
||||||
SignalInfo info;
|
SignalInfo info;
|
||||||
Kernel::KThread* active_thread;
|
Kernel::KThread* active_thread;
|
||||||
|
|
|
@ -275,11 +275,15 @@ void KProcess::RemoveSharedMemory(KSharedMemory* shmem, [[maybe_unused]] VAddr a
|
||||||
shmem->Close();
|
shmem->Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void KProcess::RegisterThread(const KThread* thread) {
|
void KProcess::RegisterThread(KThread* thread) {
|
||||||
|
KScopedLightLock lk{list_lock};
|
||||||
|
|
||||||
thread_list.push_back(thread);
|
thread_list.push_back(thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
void KProcess::UnregisterThread(const KThread* thread) {
|
void KProcess::UnregisterThread(KThread* thread) {
|
||||||
|
KScopedLightLock lk{list_lock};
|
||||||
|
|
||||||
thread_list.remove(thread);
|
thread_list.remove(thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,6 +301,50 @@ ResultCode KProcess::Reset() {
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ResultCode KProcess::SetActivity(ProcessActivity activity) {
|
||||||
|
// Lock ourselves and the scheduler.
|
||||||
|
KScopedLightLock lk{state_lock};
|
||||||
|
KScopedLightLock list_lk{list_lock};
|
||||||
|
KScopedSchedulerLock sl{kernel};
|
||||||
|
|
||||||
|
// Validate our state.
|
||||||
|
R_UNLESS(status != ProcessStatus::Exiting, ResultInvalidState);
|
||||||
|
R_UNLESS(status != ProcessStatus::Exited, ResultInvalidState);
|
||||||
|
|
||||||
|
// Either pause or resume.
|
||||||
|
if (activity == ProcessActivity::Paused) {
|
||||||
|
// Verify that we're not suspended.
|
||||||
|
if (is_suspended) {
|
||||||
|
return ResultInvalidState;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Suspend all threads.
|
||||||
|
for (auto* thread : GetThreadList()) {
|
||||||
|
thread->RequestSuspend(SuspendType::Process);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set ourselves as suspended.
|
||||||
|
SetSuspended(true);
|
||||||
|
} else {
|
||||||
|
ASSERT(activity == ProcessActivity::Runnable);
|
||||||
|
|
||||||
|
// Verify that we're suspended.
|
||||||
|
if (!is_suspended) {
|
||||||
|
return ResultInvalidState;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resume all threads.
|
||||||
|
for (auto* thread : GetThreadList()) {
|
||||||
|
thread->Resume(SuspendType::Process);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set ourselves as resumed.
|
||||||
|
SetSuspended(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
ResultCode KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata,
|
ResultCode KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata,
|
||||||
std::size_t code_size) {
|
std::size_t code_size) {
|
||||||
program_id = metadata.GetTitleID();
|
program_id = metadata.GetTitleID();
|
||||||
|
@ -556,9 +604,10 @@ bool KProcess::IsSignaled() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
KProcess::KProcess(KernelCore& kernel_)
|
KProcess::KProcess(KernelCore& kernel_)
|
||||||
: KAutoObjectWithSlabHeapAndContainer{kernel_},
|
: KAutoObjectWithSlabHeapAndContainer{kernel_}, page_table{std::make_unique<KPageTable>(
|
||||||
page_table{std::make_unique<KPageTable>(kernel_.System())}, handle_table{kernel_},
|
kernel_.System())},
|
||||||
address_arbiter{kernel_.System()}, condition_var{kernel_.System()}, state_lock{kernel_} {}
|
handle_table{kernel_}, address_arbiter{kernel_.System()}, condition_var{kernel_.System()},
|
||||||
|
state_lock{kernel_}, list_lock{kernel_} {}
|
||||||
|
|
||||||
KProcess::~KProcess() = default;
|
KProcess::~KProcess() = default;
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,11 @@ enum class ProcessStatus {
|
||||||
DebugBreak,
|
DebugBreak,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class ProcessActivity : u32 {
|
||||||
|
Runnable,
|
||||||
|
Paused,
|
||||||
|
};
|
||||||
|
|
||||||
class KProcess final : public KAutoObjectWithSlabHeapAndContainer<KProcess, KWorkerTask> {
|
class KProcess final : public KAutoObjectWithSlabHeapAndContainer<KProcess, KWorkerTask> {
|
||||||
KERNEL_AUTOOBJECT_TRAITS(KProcess, KSynchronizationObject);
|
KERNEL_AUTOOBJECT_TRAITS(KProcess, KSynchronizationObject);
|
||||||
|
|
||||||
|
@ -282,17 +287,17 @@ public:
|
||||||
u64 GetTotalPhysicalMemoryUsedWithoutSystemResource() const;
|
u64 GetTotalPhysicalMemoryUsedWithoutSystemResource() const;
|
||||||
|
|
||||||
/// Gets the list of all threads created with this process as their owner.
|
/// Gets the list of all threads created with this process as their owner.
|
||||||
const std::list<const KThread*>& GetThreadList() const {
|
std::list<KThread*>& GetThreadList() {
|
||||||
return thread_list;
|
return thread_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Registers a thread as being created under this process,
|
/// Registers a thread as being created under this process,
|
||||||
/// adding it to this process' thread list.
|
/// adding it to this process' thread list.
|
||||||
void RegisterThread(const KThread* thread);
|
void RegisterThread(KThread* thread);
|
||||||
|
|
||||||
/// Unregisters a thread from this process, removing it
|
/// Unregisters a thread from this process, removing it
|
||||||
/// from this process' thread list.
|
/// from this process' thread list.
|
||||||
void UnregisterThread(const KThread* thread);
|
void UnregisterThread(KThread* thread);
|
||||||
|
|
||||||
/// Clears the signaled state of the process if and only if it's signaled.
|
/// Clears the signaled state of the process if and only if it's signaled.
|
||||||
///
|
///
|
||||||
|
@ -347,6 +352,8 @@ public:
|
||||||
|
|
||||||
void DoWorkerTaskImpl();
|
void DoWorkerTaskImpl();
|
||||||
|
|
||||||
|
ResultCode SetActivity(ProcessActivity activity);
|
||||||
|
|
||||||
void PinCurrentThread(s32 core_id);
|
void PinCurrentThread(s32 core_id);
|
||||||
void UnpinCurrentThread(s32 core_id);
|
void UnpinCurrentThread(s32 core_id);
|
||||||
void UnpinThread(KThread* thread);
|
void UnpinThread(KThread* thread);
|
||||||
|
@ -442,7 +449,7 @@ private:
|
||||||
std::array<u64, RANDOM_ENTROPY_SIZE> random_entropy{};
|
std::array<u64, RANDOM_ENTROPY_SIZE> random_entropy{};
|
||||||
|
|
||||||
/// List of threads that are running with this process as their owner.
|
/// List of threads that are running with this process as their owner.
|
||||||
std::list<const KThread*> thread_list;
|
std::list<KThread*> thread_list;
|
||||||
|
|
||||||
/// List of shared memory that are running with this process as their owner.
|
/// List of shared memory that are running with this process as their owner.
|
||||||
std::list<KSharedMemoryInfo*> shared_memory_list;
|
std::list<KSharedMemoryInfo*> shared_memory_list;
|
||||||
|
@ -475,6 +482,7 @@ private:
|
||||||
KThread* exception_thread{};
|
KThread* exception_thread{};
|
||||||
|
|
||||||
KLightLock state_lock;
|
KLightLock state_lock;
|
||||||
|
KLightLock list_lock;
|
||||||
|
|
||||||
using TLPTree =
|
using TLPTree =
|
||||||
Common::IntrusiveRedBlackTreeBaseTraits<KThreadLocalPage>::TreeType<KThreadLocalPage>;
|
Common::IntrusiveRedBlackTreeBaseTraits<KThreadLocalPage>::TreeType<KThreadLocalPage>;
|
||||||
|
|
|
@ -267,15 +267,15 @@ ResultCode KThread::InitializeDummyThread(KThread* thread) {
|
||||||
ResultCode KThread::InitializeIdleThread(Core::System& system, KThread* thread, s32 virt_core) {
|
ResultCode KThread::InitializeIdleThread(Core::System& system, KThread* thread, s32 virt_core) {
|
||||||
return InitializeThread(thread, {}, {}, {}, IdleThreadPriority, virt_core, {}, ThreadType::Main,
|
return InitializeThread(thread, {}, {}, {}, IdleThreadPriority, virt_core, {}, ThreadType::Main,
|
||||||
Core::CpuManager::GetIdleThreadStartFunc(),
|
Core::CpuManager::GetIdleThreadStartFunc(),
|
||||||
system.GetCpuManager().GetStartFuncParamater());
|
system.GetCpuManager().GetStartFuncParameter());
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultCode KThread::InitializeHighPriorityThread(Core::System& system, KThread* thread,
|
ResultCode KThread::InitializeHighPriorityThread(Core::System& system, KThread* thread,
|
||||||
KThreadFunction func, uintptr_t arg,
|
KThreadFunction func, uintptr_t arg,
|
||||||
s32 virt_core) {
|
s32 virt_core) {
|
||||||
return InitializeThread(thread, func, arg, {}, {}, virt_core, nullptr, ThreadType::HighPriority,
|
return InitializeThread(thread, func, arg, {}, {}, virt_core, nullptr, ThreadType::HighPriority,
|
||||||
Core::CpuManager::GetSuspendThreadStartFunc(),
|
Core::CpuManager::GetShutdownThreadStartFunc(),
|
||||||
system.GetCpuManager().GetStartFuncParamater());
|
system.GetCpuManager().GetStartFuncParameter());
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultCode KThread::InitializeUserThread(Core::System& system, KThread* thread,
|
ResultCode KThread::InitializeUserThread(Core::System& system, KThread* thread,
|
||||||
|
@ -284,7 +284,7 @@ ResultCode KThread::InitializeUserThread(Core::System& system, KThread* thread,
|
||||||
system.Kernel().GlobalSchedulerContext().AddThread(thread);
|
system.Kernel().GlobalSchedulerContext().AddThread(thread);
|
||||||
return InitializeThread(thread, func, arg, user_stack_top, prio, virt_core, owner,
|
return InitializeThread(thread, func, arg, user_stack_top, prio, virt_core, owner,
|
||||||
ThreadType::User, Core::CpuManager::GetGuestThreadStartFunc(),
|
ThreadType::User, Core::CpuManager::GetGuestThreadStartFunc(),
|
||||||
system.GetCpuManager().GetStartFuncParamater());
|
system.GetCpuManager().GetStartFuncParameter());
|
||||||
}
|
}
|
||||||
|
|
||||||
void KThread::PostDestroy(uintptr_t arg) {
|
void KThread::PostDestroy(uintptr_t arg) {
|
||||||
|
|
|
@ -76,7 +76,7 @@ struct KernelCore::Impl {
|
||||||
InitializeMemoryLayout();
|
InitializeMemoryLayout();
|
||||||
Init::InitializeKPageBufferSlabHeap(system);
|
Init::InitializeKPageBufferSlabHeap(system);
|
||||||
InitializeSchedulers();
|
InitializeSchedulers();
|
||||||
InitializeSuspendThreads();
|
InitializeShutdownThreads();
|
||||||
InitializePreemption(kernel);
|
InitializePreemption(kernel);
|
||||||
|
|
||||||
RegisterHostThread();
|
RegisterHostThread();
|
||||||
|
@ -143,9 +143,9 @@ struct KernelCore::Impl {
|
||||||
CleanupObject(system_resource_limit);
|
CleanupObject(system_resource_limit);
|
||||||
|
|
||||||
for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
|
for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
|
||||||
if (suspend_threads[core_id]) {
|
if (shutdown_threads[core_id]) {
|
||||||
suspend_threads[core_id]->Close();
|
shutdown_threads[core_id]->Close();
|
||||||
suspend_threads[core_id] = nullptr;
|
shutdown_threads[core_id] = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
schedulers[core_id]->Finalize();
|
schedulers[core_id]->Finalize();
|
||||||
|
@ -247,14 +247,14 @@ struct KernelCore::Impl {
|
||||||
system.CoreTiming().ScheduleEvent(time_interval, preemption_event);
|
system.CoreTiming().ScheduleEvent(time_interval, preemption_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitializeSuspendThreads() {
|
void InitializeShutdownThreads() {
|
||||||
for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
|
for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
|
||||||
suspend_threads[core_id] = KThread::Create(system.Kernel());
|
shutdown_threads[core_id] = KThread::Create(system.Kernel());
|
||||||
ASSERT(KThread::InitializeHighPriorityThread(system, suspend_threads[core_id], {}, {},
|
ASSERT(KThread::InitializeHighPriorityThread(system, shutdown_threads[core_id], {}, {},
|
||||||
core_id)
|
core_id)
|
||||||
.IsSuccess());
|
.IsSuccess());
|
||||||
suspend_threads[core_id]->SetName(fmt::format("SuspendThread:{}", core_id));
|
shutdown_threads[core_id]->SetName(fmt::format("SuspendThread:{}", core_id));
|
||||||
suspend_threads[core_id]->DisableDispatch();
|
shutdown_threads[core_id]->DisableDispatch();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -769,7 +769,7 @@ struct KernelCore::Impl {
|
||||||
std::weak_ptr<ServiceThread> default_service_thread;
|
std::weak_ptr<ServiceThread> default_service_thread;
|
||||||
Common::ThreadWorker service_threads_manager;
|
Common::ThreadWorker service_threads_manager;
|
||||||
|
|
||||||
std::array<KThread*, Core::Hardware::NUM_CPU_CORES> suspend_threads;
|
std::array<KThread*, Core::Hardware::NUM_CPU_CORES> shutdown_threads;
|
||||||
std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES> interrupts{};
|
std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES> interrupts{};
|
||||||
std::array<std::unique_ptr<Kernel::KScheduler>, Core::Hardware::NUM_CPU_CORES> schedulers{};
|
std::array<std::unique_ptr<Kernel::KScheduler>, Core::Hardware::NUM_CPU_CORES> schedulers{};
|
||||||
|
|
||||||
|
@ -920,6 +920,12 @@ const KAutoObjectWithListContainer& KernelCore::ObjectListContainer() const {
|
||||||
return *impl->global_object_list_container;
|
return *impl->global_object_list_container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void KernelCore::InterruptAllPhysicalCores() {
|
||||||
|
for (auto& physical_core : impl->cores) {
|
||||||
|
physical_core.Interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void KernelCore::InvalidateAllInstructionCaches() {
|
void KernelCore::InvalidateAllInstructionCaches() {
|
||||||
for (auto& physical_core : impl->cores) {
|
for (auto& physical_core : impl->cores) {
|
||||||
physical_core.ArmInterface().ClearInstructionCache();
|
physical_core.ArmInterface().ClearInstructionCache();
|
||||||
|
@ -1067,17 +1073,20 @@ const Kernel::KSharedMemory& KernelCore::GetHidBusSharedMem() const {
|
||||||
return *impl->hidbus_shared_mem;
|
return *impl->hidbus_shared_mem;
|
||||||
}
|
}
|
||||||
|
|
||||||
void KernelCore::Suspend(bool in_suspention) {
|
void KernelCore::Suspend(bool suspended) {
|
||||||
const bool should_suspend = exception_exited || in_suspention;
|
const bool should_suspend{exception_exited || suspended};
|
||||||
{
|
const auto activity = should_suspend ? ProcessActivity::Paused : ProcessActivity::Runnable;
|
||||||
KScopedSchedulerLock lock(*this);
|
|
||||||
const auto state = should_suspend ? ThreadState::Runnable : ThreadState::Waiting;
|
for (auto* process : GetProcessList()) {
|
||||||
for (u32 core_id = 0; core_id < Core::Hardware::NUM_CPU_CORES; core_id++) {
|
process->SetActivity(activity);
|
||||||
impl->suspend_threads[core_id]->SetState(state);
|
|
||||||
impl->suspend_threads[core_id]->SetWaitReasonForDebugging(
|
|
||||||
ThreadWaitReasonForDebugging::Suspended);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void KernelCore::ShutdownCores() {
|
||||||
|
for (auto* thread : impl->shutdown_threads) {
|
||||||
|
void(thread->Run());
|
||||||
|
}
|
||||||
|
InterruptAllPhysicalCores();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool KernelCore::IsMulticore() const {
|
bool KernelCore::IsMulticore() const {
|
||||||
|
|
|
@ -184,6 +184,8 @@ public:
|
||||||
|
|
||||||
const std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>& Interrupts() const;
|
const std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>& Interrupts() const;
|
||||||
|
|
||||||
|
void InterruptAllPhysicalCores();
|
||||||
|
|
||||||
void InvalidateAllInstructionCaches();
|
void InvalidateAllInstructionCaches();
|
||||||
|
|
||||||
void InvalidateCpuInstructionCacheRange(VAddr addr, std::size_t size);
|
void InvalidateCpuInstructionCacheRange(VAddr addr, std::size_t size);
|
||||||
|
@ -269,12 +271,15 @@ public:
|
||||||
/// Gets the shared memory object for HIDBus services.
|
/// Gets the shared memory object for HIDBus services.
|
||||||
const Kernel::KSharedMemory& GetHidBusSharedMem() const;
|
const Kernel::KSharedMemory& GetHidBusSharedMem() const;
|
||||||
|
|
||||||
/// Suspend/unsuspend the OS.
|
/// Suspend/unsuspend all processes.
|
||||||
void Suspend(bool in_suspention);
|
void Suspend(bool suspend);
|
||||||
|
|
||||||
/// Exceptional exit the OS.
|
/// Exceptional exit all processes.
|
||||||
void ExceptionalExit();
|
void ExceptionalExit();
|
||||||
|
|
||||||
|
/// Notify emulated CPU cores to shut down.
|
||||||
|
void ShutdownCores();
|
||||||
|
|
||||||
bool IsMulticore() const;
|
bool IsMulticore() const;
|
||||||
|
|
||||||
bool IsShuttingDown() const;
|
bool IsShuttingDown() const;
|
||||||
|
|
|
@ -2530,7 +2530,7 @@ static ResultCode GetThreadList(Core::System& system, u32* out_num_threads, VAdd
|
||||||
return ResultOutOfRange;
|
return ResultOutOfRange;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto* const current_process = system.Kernel().CurrentProcess();
|
auto* const current_process = system.Kernel().CurrentProcess();
|
||||||
const auto total_copy_size = out_thread_ids_size * sizeof(u64);
|
const auto total_copy_size = out_thread_ids_size * sizeof(u64);
|
||||||
|
|
||||||
if (out_thread_ids_size > 0 &&
|
if (out_thread_ids_size > 0 &&
|
||||||
|
|
|
@ -150,9 +150,9 @@ NvResult nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector
|
||||||
event.event->GetWritableEvent().Clear();
|
event.event->GetWritableEvent().Clear();
|
||||||
if (events_interface.failed[event_id]) {
|
if (events_interface.failed[event_id]) {
|
||||||
{
|
{
|
||||||
auto lk = system.StallCPU();
|
auto lk = system.StallProcesses();
|
||||||
gpu.WaitFence(params.syncpt_id, target_value);
|
gpu.WaitFence(params.syncpt_id, target_value);
|
||||||
system.UnstallCPU();
|
system.UnstallProcesses();
|
||||||
}
|
}
|
||||||
std::memcpy(output.data(), ¶ms, sizeof(params));
|
std::memcpy(output.data(), ¶ms, sizeof(params));
|
||||||
events_interface.failed[event_id] = false;
|
events_interface.failed[event_id] = false;
|
||||||
|
|
|
@ -975,7 +975,6 @@ private:
|
||||||
Environment& env;
|
Environment& env;
|
||||||
IR::AbstractSyntaxList& syntax_list;
|
IR::AbstractSyntaxList& syntax_list;
|
||||||
bool uses_demote_to_helper{};
|
bool uses_demote_to_helper{};
|
||||||
|
|
||||||
const Flow::Block dummy_flow_block;
|
const Flow::Block dummy_flow_block;
|
||||||
};
|
};
|
||||||
} // Anonymous namespace
|
} // Anonymous namespace
|
||||||
|
|
Loading…
Reference in a new issue