Skip to content

Commit e21829d

Browse files
committed
scsp,scu: emit perf jitdump records
also factor jitdump writer into a shared translation unit
1 parent a08973c commit e21829d

5 files changed

Lines changed: 233 additions & 153 deletions

File tree

Makefile.common

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ endif
5757

5858
SOURCES_C += $(CORE_EMU_DIR)/ss.c \
5959
$(CORE_EMU_DIR)/sound_glue.c \
60-
$(CORE_EMU_DIR)/scsp_dsp_jit.c
60+
$(CORE_EMU_DIR)/scsp_dsp_jit.c \
61+
$(CORE_EMU_DIR)/jitdump.c
6162

6263
# sound.cpp -> sound.c + sound_glue.cpp (Phase-6c) -> sound_glue.c
6364
# (Phase-9). The orchestration half (SOUND_* public ABI, the

mednafen/ss/jitdump.c

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/******************************************************************************/
2+
/* Mednafen Sega Saturn Emulation Module */
3+
/******************************************************************************/
4+
/* jitdump.c - shared Linux perf jitdump writer for the DSP JITs
5+
** Copyright (C) 2026 pstef
6+
*/
7+
8+
#include "jitdump.h"
9+
10+
#if defined(WANT_DSP_JIT_PERF_DUMP) && (defined(__aarch64__) || defined(__arm64__))
11+
12+
/*
13+
* Perf jitdump writer. Produces /tmp/jit-<pid>.dump in the Linux perf
14+
* jitdump v1 format (see kernel docs Documentation/admin-guide/perf/...).
15+
* `perf record` captures the marker mmap, then `perf inject --jit` reads
16+
* the dump and emits ELF stubs so `perf report` resolves samples landing
17+
* in our code segment to per-slot symbols.
18+
*
19+
* Shared between the SCU and SCSP DSP JITs. Both backends compile under
20+
* their respective subsystem locks on the same emulator thread, so the
21+
* single fd / index counter don't need any explicit serialization here.
22+
* The atexit handler fires after that thread exits.
23+
*/
24+
25+
#include <stdint.h>
26+
#include <stdio.h>
27+
#include <stdlib.h>
28+
#include <string.h>
29+
#include <fcntl.h>
30+
#include <sys/mman.h>
31+
#include <sys/syscall.h>
32+
#include <sys/uio.h>
33+
#include <time.h>
34+
#include <unistd.h>
35+
36+
#define JITDUMP_MAGIC 0x4A695444u /* "JiTD" */
37+
#define JITDUMP_VERSION 1u
38+
#define JIT_CODE_LOAD 0u
39+
#define JIT_CODE_CLOSE 3u
40+
#define ELF_MACH_AARCH64 183u
41+
42+
struct JitdumpHeader
43+
{
44+
uint32_t magic;
45+
uint32_t version;
46+
uint32_t total_size;
47+
uint32_t elf_mach;
48+
uint32_t pad1;
49+
uint32_t pid;
50+
uint64_t timestamp;
51+
uint64_t flags;
52+
};
53+
54+
struct JitdumpRecPrefix
55+
{
56+
uint32_t id;
57+
uint32_t total_size;
58+
uint64_t timestamp;
59+
};
60+
61+
struct JitdumpRecCodeLoad
62+
{
63+
struct JitdumpRecPrefix p;
64+
uint32_t pid;
65+
uint32_t tid;
66+
uint64_t vma;
67+
uint64_t code_addr;
68+
uint64_t code_size;
69+
uint64_t code_index;
70+
/* followed by NUL-terminated name, then code_size bytes of code */
71+
};
72+
73+
static int g_jitdump_fd = -1;
74+
static void* g_jitdump_marker = NULL;
75+
static size_t g_jitdump_marker_size = 0;
76+
static uint64_t g_jitdump_index = 0;
77+
78+
static uint64_t jitdump_now_ns(void)
79+
{
80+
struct timespec ts;
81+
clock_gettime(CLOCK_MONOTONIC, &ts);
82+
return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec;
83+
}
84+
85+
static void jitdump_close(void)
86+
{
87+
struct JitdumpRecPrefix close_rec = {0};
88+
if(g_jitdump_fd < 0) return;
89+
close_rec.id = JIT_CODE_CLOSE;
90+
close_rec.total_size = sizeof(close_rec);
91+
close_rec.timestamp = jitdump_now_ns();
92+
(void)write(g_jitdump_fd, &close_rec, sizeof(close_rec));
93+
if(g_jitdump_marker) (void)munmap(g_jitdump_marker, g_jitdump_marker_size);
94+
(void)close(g_jitdump_fd);
95+
g_jitdump_fd = -1;
96+
g_jitdump_marker = NULL;
97+
g_jitdump_marker_size = 0;
98+
}
99+
100+
void SS_JitDump_Open(void)
101+
{
102+
char path[64];
103+
int fd;
104+
struct JitdumpHeader hdr = {0};
105+
long pagesz;
106+
107+
if(g_jitdump_fd >= 0) return;
108+
snprintf(path, sizeof(path), "/tmp/jit-%d.dump", (int)getpid());
109+
fd = open(path, O_CREAT | O_TRUNC | O_RDWR, 0644);
110+
if(fd < 0) return;
111+
112+
hdr.magic = JITDUMP_MAGIC;
113+
hdr.version = JITDUMP_VERSION;
114+
hdr.total_size = sizeof(hdr);
115+
hdr.elf_mach = ELF_MACH_AARCH64;
116+
hdr.pid = (uint32_t)getpid();
117+
hdr.timestamp = jitdump_now_ns();
118+
if(write(fd, &hdr, sizeof(hdr)) != (ssize_t)sizeof(hdr))
119+
{
120+
(void)close(fd);
121+
return;
122+
}
123+
124+
/* The marker mmap is what `perf record` sees in its MMAP events; that
125+
* is how `perf inject --jit` discovers our dump file. One page of
126+
* PROT_READ|PROT_EXEC at file offset 0 is the documented contract. */
127+
pagesz = sysconf(_SC_PAGESIZE);
128+
g_jitdump_marker_size = (pagesz > 0) ? (size_t)pagesz : 4096u;
129+
g_jitdump_marker = mmap(NULL, g_jitdump_marker_size,
130+
PROT_READ | PROT_EXEC, MAP_PRIVATE, fd, 0);
131+
if(g_jitdump_marker == MAP_FAILED) g_jitdump_marker = NULL;
132+
133+
g_jitdump_fd = fd;
134+
atexit(jitdump_close);
135+
}
136+
137+
void SS_JitDump_Emit(const char* name, const void* code_addr, size_t code_size)
138+
{
139+
size_t name_len;
140+
struct JitdumpRecCodeLoad rec = {0};
141+
struct iovec iov[3];
142+
143+
if(g_jitdump_fd < 0 || !code_addr || code_size == 0) return;
144+
145+
name_len = strlen(name) + 1;
146+
rec.p.id = JIT_CODE_LOAD;
147+
rec.p.total_size = (uint32_t)(sizeof(rec) + name_len + code_size);
148+
rec.p.timestamp = jitdump_now_ns();
149+
rec.pid = (uint32_t)getpid();
150+
rec.tid = (uint32_t)syscall(SYS_gettid);
151+
rec.vma = (uint64_t)(uintptr_t)code_addr;
152+
rec.code_addr = rec.vma;
153+
rec.code_size = code_size;
154+
rec.code_index = ++g_jitdump_index;
155+
156+
iov[0].iov_base = &rec;
157+
iov[0].iov_len = sizeof(rec);
158+
iov[1].iov_base = (char*)name;
159+
iov[1].iov_len = name_len;
160+
iov[2].iov_base = (void*)code_addr;
161+
iov[2].iov_len = code_size;
162+
(void)writev(g_jitdump_fd, iov, 3);
163+
}
164+
165+
#endif

mednafen/ss/jitdump.h

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/******************************************************************************/
2+
/* Mednafen Sega Saturn Emulation Module */
3+
/******************************************************************************/
4+
/* jitdump.h - shared Linux perf jitdump writer for the DSP JITs
5+
** Copyright (C) 2026 pstef
6+
*/
7+
8+
#ifndef __MDFN_SS_JITDUMP_H
9+
#define __MDFN_SS_JITDUMP_H
10+
11+
#include <stddef.h>
12+
13+
#ifdef __cplusplus
14+
extern "C" {
15+
#endif
16+
17+
/*
18+
* Single per-process jitdump stream shared by the SCU and SCSP DSP
19+
* JITs. Without sharing, each compile unit would own its own
20+
* O_TRUNC'd /tmp/jit-<pid>.dump fd and clobber the other's header.
21+
* The implementation is in jitdump.c and is compiled only when
22+
* WANT_DSP_JIT_PERF_DUMP is set and the target is aarch64; on every
23+
* other configuration the stubs below collapse to no-ops at the call
24+
* site so callers don't need their own guards.
25+
*/
26+
#if defined(WANT_DSP_JIT_PERF_DUMP) && (defined(__aarch64__) || defined(__arm64__))
27+
28+
void SS_JitDump_Open(void);
29+
void SS_JitDump_Emit(const char* name, const void* code_addr, size_t code_size);
30+
31+
#else
32+
33+
static inline void SS_JitDump_Open(void) {}
34+
static inline void SS_JitDump_Emit(const char* name, const void* code_addr, size_t code_size)
35+
{ (void)name; (void)code_addr; (void)code_size; }
36+
37+
#endif
38+
39+
#ifdef __cplusplus
40+
}
41+
#endif
42+
43+
#endif

mednafen/ss/scsp_dsp_jit.c

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
#include "scsp.h"
4949
#include "scsp_dsp_jit.h"
5050
#include "a64emit.h"
51+
#include "jitdump.h"
5152

5253
void (*SCSP_DSP_JIT_Entry)(struct SS_SCSP*) = NULL;
5354

@@ -714,8 +715,15 @@ void SCSP_DSP_JIT_Compile(struct SS_SCSP* scsp)
714715
a64_pool_flush(g_cg);
715716

716717
void* const end_addr = a64_codegen_wptr(g_cg);
717-
a64_codegen_invalidate(g_cg, entry_addr,
718-
(size_t)((char*)end_addr - (char*)entry_addr));
718+
const size_t code_bytes = (size_t)((char*)end_addr - (char*)entry_addr);
719+
a64_codegen_invalidate(g_cg, entry_addr, code_bytes);
720+
721+
/* Publish to perf jitdump. perf inject --jit will resolve samples
722+
* landing anywhere in [entry_addr, end_addr) to this symbol. The
723+
* code_index counter in the shared writer disambiguates successive
724+
* MPROG_Dirty recompiles that reuse the same address. */
725+
SS_JitDump_Open();
726+
SS_JitDump_Emit("scsp_mprog", entry_addr, code_bytes);
719727

720728
SCSP_DSP_JIT_Entry = (void(*)(struct SS_SCSP*))entry_addr;
721729
}

0 commit comments

Comments
 (0)