Files
xenia-rs/audit-runs/phase-a-diff-harness/canary-patch.diff
MechaCat02 ef93a4fa14 handoff: VSync/event-wedge fixes + iterate 2.A–2.BC research notes
Source changes (dormant parity infra, retained from iterate 2.AI/2.AO):
- xenia-kernel/exports.rs: nt_create_event manual_reset polarity +
  related event wiring
- xenia-gpu/mmio_region.rs: D1MODE_VBLANK_VLINE_STATUS hardcode parity

Also lands the audit-runs/ analysis notes (.md/.txt/.json digests) for the
iterate 2.x VSync/0x10e8/0x1004 wedge investigation. Raw trace dumps
(.jsonl/.gz/.csv/.stdout) and agent worktrees (.claude/) are gitignored as
regenerable local artifacts — see memory + HANDOFF for the running findings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 07:19:08 +02:00

586 lines
22 KiB
Diff

diff --git a/src/xenia/cpu/cpu_flags.cc b/src/xenia/cpu/cpu_flags.cc
index 3ff067e15..e57ec5a7b 100644
--- a/src/xenia/cpu/cpu_flags.cc
+++ b/src/xenia/cpu/cpu_flags.cc
@@ -57,3 +57,35 @@ DEFINE_bool(break_condition_truncate, true, "truncate value to 32-bits", "CPU");
DEFINE_bool(break_on_debugbreak, true, "int3 on JITed __debugbreak requests.",
"CPU");
+
+// AUDIT-DEMO: smoke marker (memory entry: emulator.cc:225,283). Always-on bool.
+DEFINE_bool(audit_demo_setup_trace, true,
+ "Audit smoke marker: log AUDIT-DEMO-SETUP-BEGIN at emulator setup.",
+ "Audit");
+
+// AUDIT-061: comma-separated list of guest PCs to log on each fire.
+// Format: "0xPC1,0xPC2,..." (max 32 PCs). Each fire emits
+// AUDIT-061-BR pc=X lr=X cr0=LGE cr6=LGE r3=X r4=X r5=X r6=X r31=X tid=N.
+// Default empty (off); no perf cost when empty.
+DEFINE_string(audit_61_branch_probe_pcs, "",
+ "AUDIT-061: CSV of guest PCs to trace (cr0/cr6 + regs/tid).",
+ "Audit");
+
+// AUDIT-067: comma-separated list of u32 values to watch. When non-empty,
+// every 4-byte guest store (stw/stwu/stwx/stwux/stmw) emits a runtime
+// equality check; matches log AUDIT-067-VAL pc=X lr=X val=X dst=X r3..r6 r31 tid=N.
+// Max 4 values. Default empty (off); zero overhead when empty.
+DEFINE_string(audit_67_value_watch, "",
+ "AUDIT-067: CSV of u32 values (max 4) — log every guest "
+ "store whose value matches.",
+ "Audit");
+
+// Phase A — see kernel/event_log.h.
+DEFINE_string(phase_a_event_log_path, "",
+ "Phase A: write schema-v1 JSONL event log to this path. "
+ "Empty (default) = disabled.",
+ "Audit");
+DEFINE_bool(phase_a_event_log_mem_writes, false,
+ "Phase A: include mem.write events in the JSONL log. RESERVED — "
+ "not wired in this phase. Default false.",
+ "Audit");
diff --git a/src/xenia/cpu/cpu_flags.h b/src/xenia/cpu/cpu_flags.h
index 38c4f98ba..c7f5a2711 100644
--- a/src/xenia/cpu/cpu_flags.h
+++ b/src/xenia/cpu/cpu_flags.h
@@ -35,4 +35,22 @@ DECLARE_bool(break_condition_truncate);
DECLARE_bool(break_on_debugbreak);
+// AUDIT-DEMO smoke marker.
+DECLARE_bool(audit_demo_setup_trace);
+
+// AUDIT-061: multi-PC branch probe — emits one log line per fire with
+// (pc, lr, cr0 LGE, cr6 LGE, r3, r4, r5, r6, r31, tid). CSV of guest PCs.
+DECLARE_string(audit_61_branch_probe_pcs);
+
+// AUDIT-067: value-watch — emit a log line for each 32-bit guest store whose
+// value-to-be-stored matches any configured value. CSV of u32 values
+// ("0xDEADBEEF,..."), max 4 entries. Default empty (off); zero cost when empty.
+DECLARE_string(audit_67_value_watch);
+
+// Phase A: JSONL event-log emitter path. When non-empty, the engine writes
+// schema-v1 JSONL events to this file. Empty (default) = no overhead, no
+// behavior change. Schema: xenia-rs/audit-runs/phase-a-diff-harness/schema-v1.md
+DECLARE_string(phase_a_event_log_path);
+DECLARE_bool(phase_a_event_log_mem_writes);
+
#endif // XENIA_CPU_CPU_FLAGS_H_
diff --git a/src/xenia/kernel/event_log.cc b/src/xenia/kernel/event_log.cc
new file mode 100644
index 000000000..a4ae4b41d
--- /dev/null
+++ b/src/xenia/kernel/event_log.cc
@@ -0,0 +1,335 @@
+/**
+ ******************************************************************************
+ * Xenia : Xbox 360 Emulator Research Project *
+ ******************************************************************************
+ * Phase A event-log emitter — see event_log.h and schema-v1.md.
+ ******************************************************************************
+ */
+
+#include "xenia/kernel/event_log.h"
+
+#include <atomic>
+#include <chrono>
+#include <cstdio>
+#include <cstring>
+#include <mutex>
+#include <string>
+
+#include "third_party/fmt/include/fmt/format.h"
+
+#include "xenia/base/cvar.h"
+#include "xenia/kernel/xthread.h"
+
+DECLARE_string(phase_a_event_log_path);
+
+namespace xe {
+namespace kernel {
+namespace phase_a {
+
+namespace {
+
+// Cached enabled state, computed lazily from cvar (cheap fast-path).
+std::atomic<int> g_state{0}; // 0=untouched, 1=enabled, 2=disabled
+std::FILE* g_file = nullptr;
+std::mutex g_file_mu;
+std::once_flag g_init_once;
+
+// Per-thread monotonic event index (key for the diff tool).
+thread_local uint64_t t_tid_event_idx = 0;
+
+// Process-start ns for the host_ns field. Captured on first use; debug only.
+std::chrono::steady_clock::time_point g_t0;
+std::once_flag g_t0_once;
+
+void EnsureT0() {
+ std::call_once(g_t0_once,
+ []() { g_t0 = std::chrono::steady_clock::now(); });
+}
+
+int64_t HostNsSinceStart() {
+ EnsureT0();
+ auto now = std::chrono::steady_clock::now();
+ return std::chrono::duration_cast<std::chrono::nanoseconds>(now - g_t0)
+ .count();
+}
+
+void OpenIfNeeded() {
+ std::call_once(g_init_once, []() {
+ const std::string& path = cvars::phase_a_event_log_path;
+ if (path.empty()) {
+ g_state.store(2, std::memory_order_release);
+ return;
+ }
+ g_file = std::fopen(path.c_str(), "wb");
+ if (!g_file) {
+ g_state.store(2, std::memory_order_release);
+ return;
+ }
+ g_state.store(1, std::memory_order_release);
+ // Write the schema header as the first line — synthetic tid=0.
+ auto header = fmt::format(
+ "{{\"schema_version\":1,\"engine\":\"canary\",\"kind\":\"schema_version"
+ "\",\"tid\":0,\"tid_event_idx\":0,\"guest_cycle\":0,\"host_ns\":{},\""
+ "deterministic\":true,\"payload\":{{\"version\":1,\"emitter_build\":\""
+ "canary-phaseA\"}}}}\n",
+ HostNsSinceStart());
+ std::fwrite(header.data(), 1, header.size(), g_file);
+ std::fflush(g_file);
+ });
+}
+
+uint32_t CurrentTid() {
+ // XThread::GetCurrentThreadId returns 0 if no current XThread (boot thread).
+ return XThread::GetCurrentThreadId();
+}
+
+void WriteLine(const std::string& line) {
+ std::lock_guard<std::mutex> lock(g_file_mu);
+ if (!g_file) return;
+ std::fwrite(line.data(), 1, line.size(), g_file);
+ std::fputc('\n', g_file);
+ // Flush every line so a crash mid-boot still produces a useful prefix.
+ std::fflush(g_file);
+}
+
+// Common-fields prefix. Caller appends `,\"payload\":{...}}`.
+// kind, tid, tid_event_idx, guest_cycle=0 (canary has no kernel-layer cycle),
+// host_ns, deterministic, engine.
+std::string CommonPrefix(const char* kind, uint32_t tid, uint64_t idx,
+ bool deterministic) {
+ return fmt::format(
+ "{{\"schema_version\":1,\"engine\":\"canary\",\"kind\":\"{}\",\"tid\":{},"
+ "\"tid_event_idx\":{},\"guest_cycle\":0,\"host_ns\":{},\"deterministic\":"
+ "{}",
+ kind, tid, idx, HostNsSinceStart(), deterministic ? "true" : "false");
+}
+
+// Escape a JSON string. Keep it minimal — kernel names are ASCII.
+std::string EscapeJson(const char* s) {
+ if (!s) return "null";
+ std::string out;
+ out.reserve(std::strlen(s) + 2);
+ for (const char* p = s; *p; ++p) {
+ unsigned char c = static_cast<unsigned char>(*p);
+ if (c == '\\' || c == '"') {
+ out.push_back('\\');
+ out.push_back(static_cast<char>(c));
+ } else if (c == '\n') {
+ out += "\\n";
+ } else if (c == '\r') {
+ out += "\\r";
+ } else if (c == '\t') {
+ out += "\\t";
+ } else if (c < 0x20) {
+ out += fmt::format("\\u{:04x}", c);
+ } else {
+ out.push_back(static_cast<char>(c));
+ }
+ }
+ return out;
+}
+
+} // namespace
+
+bool IsEnabled() {
+ int s = g_state.load(std::memory_order_acquire);
+ if (s == 0) {
+ OpenIfNeeded();
+ s = g_state.load(std::memory_order_acquire);
+ }
+ return s == 1;
+}
+
+uint64_t PeekTidEventIdx() { return t_tid_event_idx; }
+
+uint64_t ComputeSemanticId(uint32_t create_site_pc, uint32_t creating_tid,
+ uint64_t tid_event_idx_at_creation,
+ uint32_t object_type) {
+ uint8_t bytes[4 + 4 + 8 + 4];
+ auto put_u32 = [&](size_t off, uint32_t v) {
+ bytes[off + 0] = static_cast<uint8_t>(v & 0xFF);
+ bytes[off + 1] = static_cast<uint8_t>((v >> 8) & 0xFF);
+ bytes[off + 2] = static_cast<uint8_t>((v >> 16) & 0xFF);
+ bytes[off + 3] = static_cast<uint8_t>((v >> 24) & 0xFF);
+ };
+ auto put_u64 = [&](size_t off, uint64_t v) {
+ for (int i = 0; i < 8; ++i)
+ bytes[off + i] = static_cast<uint8_t>((v >> (i * 8)) & 0xFF);
+ };
+ put_u32(0, create_site_pc);
+ put_u32(4, creating_tid);
+ put_u64(8, tid_event_idx_at_creation);
+ put_u32(16, object_type);
+ uint64_t h = 0xCBF29CE484222325ULL;
+ for (size_t i = 0; i < sizeof(bytes); ++i) {
+ h ^= bytes[i];
+ h *= 0x100000001B3ULL;
+ }
+ return h;
+}
+
+void EmitSchemaHeader() {
+ if (!IsEnabled()) return;
+ // tid=0, tid_event_idx=0, deterministic=true. NOT consuming the per-tid
+ // counter (the header is on a synthetic tid 0).
+ std::string line = fmt::format(
+ "{{\"schema_version\":1,\"engine\":\"canary\",\"kind\":\"schema_version"
+ "\",\"tid\":0,\"tid_event_idx\":0,\"guest_cycle\":0,\"host_ns\":{},\""
+ "deterministic\":true,\"payload\":{{\"version\":1,\"emitter_build\":\""
+ "canary-phaseA\"}}}}",
+ HostNsSinceStart());
+ WriteLine(line);
+}
+
+void EmitImportCall(const char* module_name, uint16_t ordinal,
+ const char* fn_name) {
+ if (!IsEnabled()) return;
+ uint32_t tid = CurrentTid();
+ uint64_t idx = t_tid_event_idx++;
+ std::string line = CommonPrefix("import.call", tid, idx, true);
+ line += fmt::format(
+ ",\"payload\":{{\"module\":\"{}\",\"ord\":{},\"name\":\"{}\"}}}}",
+ EscapeJson(module_name), ordinal, EscapeJson(fn_name));
+ WriteLine(line);
+}
+
+void EmitKernelCall(const char* name) {
+ if (!IsEnabled()) return;
+ uint32_t tid = CurrentTid();
+ uint64_t idx = t_tid_event_idx++;
+ std::string line = CommonPrefix("kernel.call", tid, idx, true);
+ line += fmt::format(",\"payload\":{{\"name\":\"{}\",\"args\":{{}},\"args_"
+ "resolved\":{{}}}}}}",
+ EscapeJson(name));
+ WriteLine(line);
+}
+
+void EmitKernelReturn(const char* name, uint64_t return_value) {
+ if (!IsEnabled()) return;
+ uint32_t tid = CurrentTid();
+ uint64_t idx = t_tid_event_idx++;
+ std::string line = CommonPrefix("kernel.return", tid, idx, true);
+ line += fmt::format(
+ ",\"payload\":{{\"name\":\"{}\",\"return_value\":{},\"status\":\"0x{:08x}"
+ "\",\"side_effects\":[]}}}}",
+ EscapeJson(name), return_value, static_cast<uint32_t>(return_value));
+ WriteLine(line);
+}
+
+void EmitHandleCreate(uint64_t semantic_id, uint32_t object_type,
+ uint32_t raw_handle_id, const char* object_name) {
+ if (!IsEnabled()) return;
+ uint32_t tid = CurrentTid();
+ uint64_t idx = t_tid_event_idx++;
+ std::string line = CommonPrefix("handle.create", tid, idx, true);
+ if (object_name && *object_name) {
+ line += fmt::format(
+ ",\"payload\":{{\"handle_semantic_id\":\"{:016x}\",\"object_type\":{},"
+ "\"object_name\":\"{}\",\"raw_handle_id\":\"0x{:08x}\"}}}}",
+ semantic_id, object_type, EscapeJson(object_name), raw_handle_id);
+ } else {
+ line += fmt::format(
+ ",\"payload\":{{\"handle_semantic_id\":\"{:016x}\",\"object_type\":{},"
+ "\"object_name\":null,\"raw_handle_id\":\"0x{:08x}\"}}}}",
+ semantic_id, object_type, raw_handle_id);
+ }
+ WriteLine(line);
+}
+
+void EmitHandleDestroy(uint64_t semantic_id, uint32_t raw_handle_id,
+ uint32_t prior_refcount) {
+ if (!IsEnabled()) return;
+ uint32_t tid = CurrentTid();
+ uint64_t idx = t_tid_event_idx++;
+ std::string line = CommonPrefix("handle.destroy", tid, idx, true);
+ line += fmt::format(
+ ",\"payload\":{{\"handle_semantic_id\":\"{:016x}\",\"raw_handle_id\":\""
+ "0x{:08x}\",\"prior_refcount\":{}}}}}",
+ semantic_id, raw_handle_id, prior_refcount);
+ WriteLine(line);
+}
+
+void EmitThreadCreate(uint64_t semantic_id, uint32_t parent_tid,
+ uint32_t entry_pc, uint32_t ctx_ptr, uint32_t priority,
+ uint32_t affinity, uint32_t stack_size, bool suspended) {
+ if (!IsEnabled()) return;
+ uint32_t tid = CurrentTid();
+ uint64_t idx = t_tid_event_idx++;
+ std::string line = CommonPrefix("thread.create", tid, idx, true);
+ line += fmt::format(
+ ",\"payload\":{{\"handle_semantic_id\":\"{:016x}\",\"parent_tid\":{},"
+ "\"entry_pc\":\"0x{:08x}\",\"ctx_ptr\":\"0x{:08x}\",\"priority\":{},"
+ "\"affinity\":{},\"stack_size\":{},\"suspended\":{}}}}}",
+ semantic_id, parent_tid, entry_pc, ctx_ptr, priority, affinity,
+ stack_size, suspended ? "true" : "false");
+ WriteLine(line);
+}
+
+void EmitThreadExit(uint32_t exit_code) {
+ if (!IsEnabled()) return;
+ uint32_t tid = CurrentTid();
+ uint64_t idx = t_tid_event_idx++;
+ std::string line = CommonPrefix("thread.exit", tid, idx, true);
+ line += fmt::format(",\"payload\":{{\"exit_code\":{}}}}}", exit_code);
+ WriteLine(line);
+}
+
+void EmitWaitBegin(const uint64_t* handles_semantic_ids, uint32_t count,
+ int64_t timeout_ns, bool alertable, bool wait_all) {
+ if (!IsEnabled()) return;
+ uint32_t tid = CurrentTid();
+ uint64_t idx = t_tid_event_idx++;
+ std::string line = CommonPrefix("wait.begin", tid, idx, true);
+ std::string ids = "[";
+ for (uint32_t i = 0; i < count; ++i) {
+ if (i) ids += ",";
+ ids += fmt::format("\"{:016x}\"", handles_semantic_ids[i]);
+ }
+ ids += "]";
+ line += fmt::format(
+ ",\"payload\":{{\"handles_semantic_ids\":{},\"timeout_ns\":{},"
+ "\"alertable\":{},\"wait_type\":\"{}\"}}}}",
+ ids, timeout_ns, alertable ? "true" : "false",
+ wait_all ? "all" : "any");
+ WriteLine(line);
+}
+
+void EmitWaitEnd(uint32_t status, uint64_t woken_by_semantic_id_or_zero) {
+ if (!IsEnabled()) return;
+ uint32_t tid = CurrentTid();
+ uint64_t idx = t_tid_event_idx++;
+ std::string line = CommonPrefix("wait.end", tid, idx, false);
+ if (woken_by_semantic_id_or_zero) {
+ line += fmt::format(
+ ",\"payload\":{{\"status\":\"0x{:08x}\",\"woken_by_semantic_id\":\""
+ "{:016x}\",\"wait_duration_cycles\":0}}}}",
+ status, woken_by_semantic_id_or_zero);
+ } else {
+ line += fmt::format(
+ ",\"payload\":{{\"status\":\"0x{:08x}\",\"woken_by_semantic_id\":null,"
+ "\"wait_duration_cycles\":0}}}}",
+ status);
+ }
+ WriteLine(line);
+}
+
+} // namespace phase_a
+
+// Bridge entry points referenced from shim_utils.h. Defined here so the
+// template-heavy header does not need to include event_log.h directly.
+namespace shim {
+namespace phase_a_bridge {
+bool Enabled() { return ::xe::kernel::phase_a::IsEnabled(); }
+void EmitImportAndCall(const char* module_name, uint16_t ord,
+ const char* name) {
+ ::xe::kernel::phase_a::EmitImportCall(module_name, ord, name);
+ ::xe::kernel::phase_a::EmitKernelCall(name);
+}
+void EmitReturn(const char* name, uint64_t return_value) {
+ ::xe::kernel::phase_a::EmitKernelReturn(name, return_value);
+}
+} // namespace phase_a_bridge
+} // namespace shim
+
+} // namespace kernel
+} // namespace xe
diff --git a/src/xenia/kernel/event_log.h b/src/xenia/kernel/event_log.h
new file mode 100644
index 000000000..c51e71b61
--- /dev/null
+++ b/src/xenia/kernel/event_log.h
@@ -0,0 +1,84 @@
+/**
+ ******************************************************************************
+ * Xenia : Xbox 360 Emulator Research Project *
+ ******************************************************************************
+ * Phase A event-log emitter. Cvar-gated (default off). Schema v1.
+ * Companion: xenia-rs/audit-runs/phase-a-diff-harness/schema-v1.md
+ ******************************************************************************
+ */
+
+#ifndef XENIA_KERNEL_EVENT_LOG_H_
+#define XENIA_KERNEL_EVENT_LOG_H_
+
+#include <cstdint>
+
+namespace xe {
+namespace kernel {
+namespace phase_a {
+
+// Object-type codes (must match ours's enum exactly — see schema-v1.md).
+enum ObjectType : uint32_t {
+ kObjUnknown = 0x00,
+ kObjEvent = 0x01,
+ kObjMutant = 0x02,
+ kObjSemaphore = 0x03,
+ kObjTimer = 0x04,
+ kObjThread = 0x05,
+ kObjFile = 0x06,
+ kObjIoCompletion = 0x07,
+ kObjModule = 0x08,
+ kObjEnumState = 0x09,
+ kObjSection = 0x0A,
+ kObjNotification = 0x0B,
+};
+
+// Fast bool check (default off). Inlinable so we can guard hot paths cheaply.
+bool IsEnabled();
+
+// Emitted once at startup if enabled (first line of the JSONL).
+void EmitSchemaHeader();
+
+// FNV-1a 64-bit identity (see schema-v1.md). Both engines compute identically.
+uint64_t ComputeSemanticId(uint32_t create_site_pc, uint32_t creating_tid,
+ uint64_t tid_event_idx_at_creation,
+ uint32_t object_type);
+
+// One emit per imported kernel function invocation. Emitted by the export
+// trampoline before the kernel.call event.
+void EmitImportCall(const char* module_name, uint16_t ordinal,
+ const char* fn_name);
+
+// Kernel call entry / return. args/args_resolved are deferred to a later
+// phase; v1 emits the name + return value only (sufficient for the diff
+// tool to align by sequence).
+void EmitKernelCall(const char* name);
+void EmitKernelReturn(const char* name, uint64_t return_value);
+
+// Handle lifecycle. raw_handle_id is engine-local; the diff key is the
+// FNV-1a semantic id.
+void EmitHandleCreate(uint64_t semantic_id, uint32_t object_type,
+ uint32_t raw_handle_id, const char* object_name);
+void EmitHandleDestroy(uint64_t semantic_id, uint32_t raw_handle_id,
+ uint32_t prior_refcount);
+
+// Thread create/exit. parent_tid is the caller; entry_pc is the spawned
+// thread's first instruction.
+void EmitThreadCreate(uint64_t semantic_id, uint32_t parent_tid,
+ uint32_t entry_pc, uint32_t ctx_ptr, uint32_t priority,
+ uint32_t affinity, uint32_t stack_size, bool suspended);
+void EmitThreadExit(uint32_t exit_code);
+
+// Wait begin/end. handles_count + handles_semantic_ids array.
+void EmitWaitBegin(const uint64_t* handles_semantic_ids, uint32_t count,
+ int64_t timeout_ns, bool alertable, bool wait_all);
+void EmitWaitEnd(uint32_t status, uint64_t woken_by_semantic_id_or_zero);
+
+// Returns the next per-tid event index (post-increment). Useful for
+// `tid_event_idx_at_creation` capture before calling ComputeSemanticId.
+uint64_t PeekTidEventIdx();
+
+} // namespace phase_a
+} // namespace kernel
+} // namespace xe
+
+#endif // XENIA_KERNEL_EVENT_LOG_H_
diff --git a/src/xenia/kernel/util/shim_utils.h b/src/xenia/kernel/util/shim_utils.h
index 0fa254157..209eeed97 100644
--- a/src/xenia/kernel/util/shim_utils.h
+++ b/src/xenia/kernel/util/shim_utils.h
@@ -499,6 +499,22 @@ enum class KernelModuleId {
xbdm,
};
+// Phase A bridge — see kernel/event_log.h. Inline to avoid pulling the
+// header into shim_utils.h's transitive set.
+namespace phase_a_bridge {
+constexpr const char* KernelModuleIdName(KernelModuleId m) {
+ switch (m) {
+ case KernelModuleId::xboxkrnl: return "xboxkrnl.exe";
+ case KernelModuleId::xam: return "xam.xex";
+ case KernelModuleId::xbdm: return "xbdm.xex";
+ }
+ return "unknown";
+}
+bool Enabled();
+void EmitImportAndCall(const char* module_name, uint16_t ord, const char* name);
+void EmitReturn(const char* name, uint64_t return_value);
+} // namespace phase_a_bridge
+
template <size_t I = 0, typename... Ps>
requires(I == sizeof...(Ps))
void AppendKernelCallParams(StringBuffer& string_buffer,
@@ -578,9 +594,18 @@ struct ExportRegistrerHelper {
cvars::log_high_frequency_kernel_calls)) {
PrintKernelCall(export_entry, params);
}
+ const bool phase_a_on = phase_a_bridge::Enabled();
+ if (phase_a_on) {
+ phase_a_bridge::EmitImportAndCall(
+ phase_a_bridge::KernelModuleIdName(MODULE), ORDINAL,
+ export_entry->name);
+ }
if constexpr (std::is_void<R>::value) {
KernelTrampoline(fn, std::forward<std::tuple<Ps...>>(params),
std::make_index_sequence<sizeof...(Ps)>());
+ if (phase_a_on) {
+ phase_a_bridge::EmitReturn(export_entry->name, 0);
+ }
} else {
auto result =
KernelTrampoline(fn, std::forward<std::tuple<Ps...>>(params),
@@ -590,6 +615,11 @@ struct ExportRegistrerHelper {
(xe::cpu::ExportTag::kLog | xe::cpu::ExportTag::kLogResult)) {
// TODO(benvanik): log result.
}
+ if (phase_a_on) {
+ phase_a_bridge::EmitReturn(
+ export_entry->name,
+ static_cast<uint64_t>(ppc_context->r[3]));
+ }
}
}
};
@@ -600,14 +630,28 @@ struct ExportRegistrerHelper {
0,
};
std::tuple<Ps...> params = {Ps(init)...};
+ const bool phase_a_on = phase_a_bridge::Enabled();
+ if (phase_a_on) {
+ phase_a_bridge::EmitImportAndCall(
+ phase_a_bridge::KernelModuleIdName(MODULE), ORDINAL,
+ export_entry->name);
+ }
if constexpr (std::is_void<R>::value) {
KernelTrampoline(fn, std::forward<std::tuple<Ps...>>(params),
std::make_index_sequence<sizeof...(Ps)>());
+ if (phase_a_on) {
+ phase_a_bridge::EmitReturn(export_entry->name, 0);
+ }
} else {
auto result =
KernelTrampoline(fn, std::forward<std::tuple<Ps...>>(params),
std::make_index_sequence<sizeof...(Ps)>());
result.Store(ppc_context);
+ if (phase_a_on) {
+ phase_a_bridge::EmitReturn(
+ export_entry->name,
+ static_cast<uint64_t>(ppc_context->r[3]));
+ }
}
}
};