Compare commits

..

6 Commits

Author SHA1 Message Date
lizzie 9fac15716d Fix license headers 2026-09-20 14:43:02 +00:00
lizzie 565f9a3005 2026-09-20 13:20:40
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-20 13:20:40 +00:00
lizzie 01f149ddfd 2026-09-20 13:02:52
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-20 13:02:52 +00:00
lizzie 4bcc2a2319 2026-09-20 12:52:02
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-20 12:52:02 +00:00
lizzie 8b03a1ba01 Fix license headers 2026-09-20 07:46:07 +00:00
lizzie 652ab5c25c 2026-09-20 07:03:36
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-20 07:46:07 +00:00
13 changed files with 117 additions and 97 deletions
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
@@ -35,7 +38,7 @@ void CircularBufferSinkInfo::Update(BehaviorInfo::ErrorInfo& error_info, OutStat
auto current_params{reinterpret_cast<CircularBufferInParameter*>(parameter.data())}; auto current_params{reinterpret_cast<CircularBufferInParameter*>(parameter.data())};
auto current_state{reinterpret_cast<CircularBufferState*>(state.data())}; auto current_state{reinterpret_cast<CircularBufferState*>(state.data())};
if (in_use == buffer_params->in_use && !buffer_unmapped) { if (in_use == bool(buffer_params->in_use) && !buffer_unmapped) {
error_info.error_code = ResultSuccess; error_info.error_code = ResultSuccess;
error_info.address = CpuAddr(0); error_info.address = CpuAddr(0);
out_status.writeOffset = current_state->last_pos2; out_status.writeOffset = current_state->last_pos2;
+19 -20
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
@@ -31,10 +31,10 @@ public:
}; };
struct DeviceInParameter { struct DeviceInParameter {
/* 0x000 */ char name[0x100]; /* 0x000 */ u8 name[0x100];
/* 0x100 */ u32 input_count; /* 0x100 */ u32 input_count;
/* 0x104 */ std::array<s8, MaxChannels> inputs; /* 0x104 */ std::array<s8, MaxChannels> inputs;
/* 0x10A */ char unk10A[0x1]; /* 0x10A */ u8 unk10A[0x1];
/* 0x10B */ bool downmix_enabled; /* 0x10B */ bool downmix_enabled;
/* 0x10C */ std::array<f32, 4> downmix_coeff; /* 0x10C */ std::array<f32, 4> downmix_coeff;
}; };
@@ -43,7 +43,7 @@ public:
struct DeviceState { struct DeviceState {
/* 0x00 */ UpsamplerInfo* upsampler_info; /* 0x00 */ UpsamplerInfo* upsampler_info;
/* 0x08 */ std::array<Common::FixedPoint<16, 16>, 4> downmix_coeff; /* 0x08 */ std::array<Common::FixedPoint<16, 16>, 4> downmix_coeff;
/* 0x18 */ char unk18[0x18]; /* 0x18 */ u8 unk18[0x18];
}; };
static_assert(sizeof(DeviceState) == 0x30, "DeviceState has the wrong size!"); static_assert(sizeof(DeviceState) == 0x30, "DeviceState has the wrong size!");
@@ -55,8 +55,8 @@ public:
/* 0x14 */ u32 previous_pos; /* 0x14 */ u32 previous_pos;
/* 0x18 */ SampleFormat format; /* 0x18 */ SampleFormat format;
/* 0x1C */ std::array<s8, MaxChannels> inputs; /* 0x1C */ std::array<s8, MaxChannels> inputs;
/* 0x22 */ bool in_use; /* 0x22 */ u8 in_use;
/* 0x23 */ char unk23[0x5]; /* 0x23 */ u8 unk23[0x5];
}; };
static_assert(sizeof(CircularBufferInParameter) == 0x28, static_assert(sizeof(CircularBufferInParameter) == 0x28,
"CircularBufferInParameter has the wrong size!"); "CircularBufferInParameter has the wrong size!");
@@ -65,16 +65,16 @@ public:
/* 0x00 */ u32 last_pos2; /* 0x00 */ u32 last_pos2;
/* 0x04 */ s32 current_pos; /* 0x04 */ s32 current_pos;
/* 0x08 */ u32 last_pos; /* 0x08 */ u32 last_pos;
/* 0x0C */ char unk0C[0x4]; /* 0x0C */ u8 unk0C[0x4];
/* 0x10 */ AddressInfo address_info; /* 0x10 */ AddressInfo address_info;
}; };
static_assert(sizeof(CircularBufferState) == 0x30, "CircularBufferState has the wrong size!"); static_assert(sizeof(CircularBufferState) == 0x30, "CircularBufferState has the wrong size!");
struct InParameter { struct InParameter {
/* 0x000 */ Type type; /* 0x000 */ Type type;
/* 0x001 */ bool in_use; /* 0x001 */ u8 in_use;
/* 0x004 */ u32 node_id; /* 0x004 */ u32 node_id;
/* 0x008 */ char unk08[0x18]; /* 0x008 */ u8 unk08[0x18];
union { union {
/* 0x020 */ DeviceInParameter device; /* 0x020 */ DeviceInParameter device;
/* 0x020 */ CircularBufferInParameter circular_buffer; /* 0x020 */ CircularBufferInParameter circular_buffer;
@@ -84,7 +84,7 @@ public:
struct OutStatus { struct OutStatus {
/* 0x00 */ u32 writeOffset; /* 0x00 */ u32 writeOffset;
/* 0x04 */ char unk04[0x1C]; /* 0x04 */ u8 unk04[0x1C];
}; // size == 0x20 }; // size == 0x20
static_assert(sizeof(OutStatus) == 0x20, "SinkInfoBase::OutStatus has the wrong size!"); static_assert(sizeof(OutStatus) == 0x20, "SinkInfoBase::OutStatus has the wrong size!");
@@ -162,19 +162,18 @@ public:
u8* GetParameter(); u8* GetParameter();
protected: protected:
/// Type of this sink
Type type{Type::Invalid};
/// Is this sink in use?
bool in_use{};
/// Is this sink's buffer unmapped? Circular only
bool buffer_unmapped{};
/// Node id for this sink
u32 node_id{};
/// State buffer for this sink /// State buffer for this sink
std::array<u8, (std::max)(sizeof(DeviceState), sizeof(CircularBufferState))> state{}; std::array<u8, (std::max)(sizeof(DeviceState), sizeof(CircularBufferState))> state{};
/// Parameter buffer for this sink /// Parameter buffer for this sink
std::array<u8, (std::max)(sizeof(DeviceInParameter), sizeof(CircularBufferInParameter))> std::array<u8, (std::max)(sizeof(DeviceInParameter), sizeof(CircularBufferInParameter))> parameter{};
parameter{}; /// Type of this sink
Type type{Type::Invalid};
/// Node id for this sink
u32 node_id{};
/// Is this sink in use?
bool in_use : 1 = false;
/// Is this sink's buffer unmapped? Circular only
bool buffer_unmapped : 1 = false;
}; };
} // namespace AudioCore::Renderer } // namespace AudioCore::Renderer
+1 -1
View File
@@ -428,8 +428,8 @@ struct System::Impl {
core_timing.SyncPause(false); core_timing.SyncPause(false);
Network::CancelPendingSocketOperations(); Network::CancelPendingSocketOperations();
kernel.SuspendEmulation(true); kernel.SuspendEmulation(true);
kernel.CloseServices();
kernel.ShutdownCores(); kernel.ShutdownCores();
kernel.CloseServices();
services.reset(); services.reset();
service_manager.reset(); service_manager.reset();
fs_controller.Reset(); fs_controller.Reset();
@@ -72,11 +72,9 @@ void GlobalSchedulerContext::UnregisterDummyThreadForWakeup(KThread* thread) noe
void GlobalSchedulerContext::WakeupWaitingDummyThreads(KernelCore& kernel) noexcept { void GlobalSchedulerContext::WakeupWaitingDummyThreads(KernelCore& kernel) noexcept {
ASSERT(this->IsLocked()); ASSERT(this->IsLocked());
if (m_woken_dummy_threads.size() > 0) { for (auto* thread : m_woken_dummy_threads)
for (auto* thread : m_woken_dummy_threads) thread->DummyThreadEndWait(kernel);
thread->DummyThreadEndWait(kernel); m_woken_dummy_threads.clear();
m_woken_dummy_threads.clear();
}
} }
} // namespace Kernel } // namespace Kernel
+3 -7
View File
@@ -1236,14 +1236,10 @@ namespace Kernel {
KScopedSchedulerLock sl{kernel}; KScopedSchedulerLock sl{kernel};
// Determine if this is the first termination request. // Determine if this is the first termination request.
const bool first_request = [&]() -> bool { // Perform an atomic compare-and-swap from false to true.
// Perform an atomic compare-and-swap from false to true. bool expected = false;
bool expected = false;
return m_termination_requested.compare_exchange_strong(expected, true);
}();
// If this is the first request, start termination procedure. // If this is the first request, start termination procedure.
if (first_request) { if (m_termination_requested.compare_exchange_strong(expected, true)) {
// If the thread is in initialized state, just change state to terminated. // If the thread is in initialized state, just change state to terminated.
if (this->GetState() == ThreadState::Initialized) { if (this->GetState() == ThreadState::Initialized) {
m_thread_state = ThreadState::Terminated; m_thread_state = ThreadState::Terminated;
+9 -11
View File
@@ -967,12 +967,16 @@ Kernel::KHardwareTimer& KernelCore::HardwareTimer() {
return *impl->hardware_timer; return *impl->hardware_timer;
} }
KAutoObjectWithListContainer& KernelCore::ObjectListContainer() { KAutoObjectWithListContainer* KernelCore::ObjectListContainer() {
return *impl->global_object_list_container; if (!impl->global_object_list_container)
return nullptr;
return std::addressof(*impl->global_object_list_container);
} }
const KAutoObjectWithListContainer& KernelCore::ObjectListContainer() const { const KAutoObjectWithListContainer* KernelCore::ObjectListContainer() const {
return *impl->global_object_list_container; if (!impl->global_object_list_container)
return nullptr;
return std::addressof(*impl->global_object_list_container);
} }
void KernelCore::PrepareReschedule(std::size_t id) { void KernelCore::PrepareReschedule(std::size_t id) {
@@ -1001,16 +1005,10 @@ void KernelCore::UnregisterInUseObject(KAutoObject* object) {
void KernelCore::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) { void KernelCore::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) {
auto* manager = server_manager.get(); auto* manager = server_manager.get();
if (!impl->is_shutting_down) {
{
std::scoped_lock lk{impl->server_lock}; std::scoped_lock lk{impl->server_lock};
if (impl->is_shutting_down) {
return;
}
impl->server_managers.emplace_back(std::move(server_manager)); impl->server_managers.emplace_back(std::move(server_manager));
} }
manager->LoopProcess(); manager->LoopProcess();
} }
+2 -3
View File
@@ -175,9 +175,8 @@ public:
/// Stops execution of 'id' core, in order to reschedule a new thread. /// Stops execution of 'id' core, in order to reschedule a new thread.
void PrepareReschedule(std::size_t id); void PrepareReschedule(std::size_t id);
KAutoObjectWithListContainer& ObjectListContainer(); KAutoObjectWithListContainer* ObjectListContainer();
const KAutoObjectWithListContainer* ObjectListContainer() const;
const KAutoObjectWithListContainer& ObjectListContainer() const;
/// Registers all kernel objects with the global emulation state, this is purely for tracking /// Registers all kernel objects with the global emulation state, this is purely for tracking
/// leaks after emulation has been shutdown. /// leaks after emulation has been shutdown.
+4 -3
View File
@@ -151,7 +151,8 @@ public:
const bool is_initialized = this->IsInitialized(); const bool is_initialized = this->IsInitialized();
uintptr_t arg = 0; uintptr_t arg = 0;
if (is_initialized) { if (is_initialized) {
kernel.ObjectListContainer().Unregister(this); if (auto const olc = kernel.ObjectListContainer(); olc)
olc->Unregister(this);
arg = this->GetPostDestroyArgument(); arg = this->GetPostDestroyArgument();
this->Finalize(kernel); this->Finalize(kernel);
} }
@@ -175,7 +176,7 @@ public:
public: public:
static void InitializeSlabHeap(KernelCore& kernel, void* memory, size_t memory_size) { static void InitializeSlabHeap(KernelCore& kernel, void* memory, size_t memory_size) {
kernel.SlabHeap<Derived>().Initialize(memory, memory_size); kernel.SlabHeap<Derived>().Initialize(memory, memory_size);
kernel.ObjectListContainer().Initialize(); kernel.ObjectListContainer()->Initialize();
} }
static Derived* Create(KernelCore& kernel) { static Derived* Create(KernelCore& kernel) {
@@ -186,7 +187,7 @@ public:
} }
static void Register(KernelCore& kernel, Derived* obj) { static void Register(KernelCore& kernel, Derived* obj) {
return kernel.ObjectListContainer().Register(obj); return kernel.ObjectListContainer()->Register(obj);
} }
static size_t GetObjectSize(KernelCore& kernel) { static size_t GetObjectSize(KernelCore& kernel) {
+18 -1
View File
@@ -1,6 +1,15 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
// gcc14 bug
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
#include "common/assert.h" #include "common/assert.h"
#include "common/scope_exit.h" #include "common/scope_exit.h"
#include "core/memory/dmnt_cheat_types.h" #include "core/memory/dmnt_cheat_types.h"
@@ -223,7 +232,10 @@ DmntCheatVm::Callbacks::~Callbacks() = default;
bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) { bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
// If we've ever seen a decode failure, return false. // If we've ever seen a decode failure, return false.
bool valid = decode_success; bool valid = decode_success;
CheatVmOpcode opcode = {}; CheatVmOpcode opcode = {
.begin_conditional_block = false,
.opcode = {}
};
SCOPE_EXIT { SCOPE_EXIT {
decode_success &= valid; decode_success &= valid;
if (valid) { if (valid) {
@@ -1266,3 +1278,8 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
} }
} // namespace Core::Memory } // namespace Core::Memory
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
+11 -9
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
@@ -254,14 +254,16 @@ struct UnrecognizedInstruction {
struct CheatVmOpcode { struct CheatVmOpcode {
bool begin_conditional_block{}; bool begin_conditional_block{};
std::variant<StoreStaticOpcode, BeginConditionalOpcode, EndConditionalOpcode, ControlLoopOpcode, std::variant<
LoadRegisterStaticOpcode, LoadRegisterMemoryOpcode, StoreStaticToAddressOpcode, std::monostate,
PerformArithmeticStaticOpcode, BeginKeypressConditionalOpcode, StoreStaticOpcode, BeginConditionalOpcode, EndConditionalOpcode, ControlLoopOpcode,
PerformArithmeticRegisterOpcode, StoreRegisterToAddressOpcode, LoadRegisterStaticOpcode, LoadRegisterMemoryOpcode, StoreStaticToAddressOpcode,
BeginRegisterConditionalOpcode, SaveRestoreRegisterOpcode, PerformArithmeticStaticOpcode, BeginKeypressConditionalOpcode,
SaveRestoreRegisterMaskOpcode, ReadWriteStaticRegisterOpcode, PauseProcessOpcode, PerformArithmeticRegisterOpcode, StoreRegisterToAddressOpcode,
ResumeProcessOpcode, DebugLogOpcode, UnrecognizedInstruction> BeginRegisterConditionalOpcode, SaveRestoreRegisterOpcode,
opcode{}; SaveRestoreRegisterMaskOpcode, ReadWriteStaticRegisterOpcode, PauseProcessOpcode,
ResumeProcessOpcode, DebugLogOpcode, UnrecognizedInstruction
> opcode{};
}; };
class DmntCheatVm { class DmntCheatVm {
@@ -170,13 +170,19 @@ template<>
// check for marked bit, use as unmapped if marked // check for marked bit, use as unmapped if marked
if (ctx.conf.page_table_marked_bit) { if (ctx.conf.page_table_marked_bit) {
code.bt(page, *ctx.conf.page_table_marked_bit); auto const marked_bit = *ctx.conf.page_table_marked_bit;
code.jc(abort, code.T_NEAR); if (s64(s32(1 << marked_bit)) == s64(1 << marked_bit)) {
code.test(page, s32(1 << marked_bit));
code.jnz(abort, code.T_NEAR);
} else {
code.bt(page, marked_bit);
code.jc(abort, code.T_NEAR);
}
} }
// mask away attributes // mask away attributes
if (ctx.conf.page_table_pointer_mask == 0) { if (ctx.conf.page_table_pointer_mask == 0) {
code.test(page, page); code.test(page, page);
} else if (std::in_range<s32>(ctx.conf.page_table_pointer_mask)) { } else if (s64(s32(ctx.conf.page_table_pointer_mask)) == s64(ctx.conf.page_table_pointer_mask)) {
code.and_(page, ctx.conf.page_table_pointer_mask); code.and_(page, ctx.conf.page_table_pointer_mask);
} else { } else {
code.mov(tmp, ctx.conf.page_table_pointer_mask); code.mov(tmp, ctx.conf.page_table_pointer_mask);
@@ -79,8 +79,10 @@ void NpadAbstractButtonHandler::UpdateAllButtonLifo() {
Core::HID::NpadIdType npad_id = properties_handler->GetNpadId(); Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
for (std::size_t i = 0; i < AruidIndexMax; i++) { for (std::size_t i = 0; i < AruidIndexMax; i++) {
auto* data = applet_resource_holder->applet_resource->GetAruidDataByIndex(i); auto* data = applet_resource_holder->applet_resource->GetAruidDataByIndex(i);
auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)]; if (auto const shfmt = data->shared_memory_format; shfmt) {
UpdateButtonLifo(npad_entry, data->aruid); auto& npad_entry = shfmt->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
UpdateButtonLifo(npad_entry, data->aruid);
}
} }
} }
@@ -88,19 +90,19 @@ void NpadAbstractButtonHandler::UpdateCoreBatteryState() {
Core::HID::NpadIdType npad_id = properties_handler->GetNpadId(); Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
for (std::size_t i = 0; i < AruidIndexMax; i++) { for (std::size_t i = 0; i < AruidIndexMax; i++) {
auto* data = applet_resource_holder->applet_resource->GetAruidDataByIndex(i); auto* data = applet_resource_holder->applet_resource->GetAruidDataByIndex(i);
auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)]; if (auto const shfmt = data->shared_memory_format; shfmt) {
UpdateButtonLifo(npad_entry, data->aruid); auto& npad_entry = shfmt->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
UpdateButtonLifo(npad_entry, data->aruid);
}
} }
} }
void NpadAbstractButtonHandler::UpdateButtonState(u64 aruid) { void NpadAbstractButtonHandler::UpdateButtonState(u64 aruid) {
Core::HID::NpadIdType npad_id = properties_handler->GetNpadId(); Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
auto* data = applet_resource_holder->applet_resource->GetAruidData(aruid); if (auto data = applet_resource_holder->applet_resource->GetAruidData(aruid); data) {
if (data == nullptr) { auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
return; UpdateButtonLifo(npad_entry, aruid);
} }
auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
UpdateButtonLifo(npad_entry, aruid);
} }
Result NpadAbstractButtonHandler::SetHomeProtection(bool is_enabled, u64 aruid) { Result NpadAbstractButtonHandler::SetHomeProtection(bool is_enabled, u64 aruid) {
@@ -496,31 +496,30 @@ void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ct
} }
void PatchPhiNodes(IR::Program& program, EmitContext& ctx) { void PatchPhiNodes(IR::Program& program, EmitContext& ctx) {
// Flatten all leading PHIs from each block into a vector // Flatten all leading PHIs from each block into a vector
std::vector<IR::Inst*> phi_instructions; std::vector<IR::Inst*> phi_instructions;
for (IR::Block* block : program.blocks) { for (IR::Block* block : program.blocks) {
for (auto it = block->begin(); it != block->end(); ++it) { for (auto it = block->begin(); it != block->end(); ++it) {
if (it->GetOpcode() != IR::Opcode::Phi) if (it->GetOpcode() != IR::Opcode::Phi)
break; break;
phi_instructions.push_back(&*it); phi_instructions.push_back(&*it);
}
}
if (phi_instructions.empty()) {
return; // nothing to patch
}
// Start "before" first PHI; advance on phi_arg == 0
size_t phi_index = static_cast<size_t>(-1);
ctx.PatchDeferredPhi([&](size_t phi_arg, Id parent) -> std::pair<Id, Id> {
if (phi_arg == 0) {
++phi_index;
}
IR::Inst* phi = phi_instructions[phi_index];
return { ctx.Def(phi->Arg(phi_arg)), parent };
});
} }
}
if (phi_instructions.empty()) {
return; // nothing to patch
}
// Start "before" first PHI; advance on phi_arg == 0
size_t phi_index = size_t(-1);
ctx.PatchDeferredPhi([&ctx, &phi_index, phi_insts = std::move(phi_instructions)](size_t phi_arg, Id parent) -> std::pair<Id, Id> {
if (phi_arg == 0) {
++phi_index;
}
IR::Inst* phi = phi_insts[phi_index];
return { ctx.Def(phi->Arg(phi_arg)), parent };
});
}
} // Anonymous namespace } // Anonymous namespace
std::vector<u32> EmitSPIRV(const Profile& profile, const RuntimeInfo& runtime_info, IR::Program& program, Bindings& bindings) { std::vector<u32> EmitSPIRV(const Profile& profile, const RuntimeInfo& runtime_info, IR::Program& program, Bindings& bindings) {