mirror of
https://github.com/n64dev/cen64.git
synced 2024-06-22 22:12:45 -04:00
Add profiling support
This commit is contained in:
parent
a8779878e9
commit
9812f78917
7
cen64.c
7
cen64.c
|
@ -182,7 +182,8 @@ int cen64_main(int argc, const char **argv) {
|
|||
|
||||
if (device_create(device, &ddipl, dd_variant, &ddrom,
|
||||
&pifrom, &cart, &eeprom, &sram,
|
||||
&flashram, is_in, controller, options.no_audio, options.no_video) == NULL) {
|
||||
&flashram, is_in, controller,
|
||||
options.no_audio, options.no_video, options.enable_profiling) == NULL) {
|
||||
printf("Failed to create a device.\n");
|
||||
status = EXIT_FAILURE;
|
||||
}
|
||||
|
@ -190,7 +191,7 @@ int cen64_main(int argc, const char **argv) {
|
|||
else {
|
||||
device->multithread = options.multithread;
|
||||
status = run_device(device, options.no_video);
|
||||
device_destroy(device);
|
||||
device_destroy(device, options.cart_path);
|
||||
}
|
||||
|
||||
cen64_free(&cen64_device_mem);
|
||||
|
@ -424,7 +425,7 @@ int run_device(struct cen64_device *device, bool no_video) {
|
|||
|
||||
if (cen64_thread_create(&thread, run_device_thread, device)) {
|
||||
printf("Failed to create the main emulation thread.\n");
|
||||
device_destroy(device);
|
||||
device_destroy(device, NULL);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include "vr4300/cpu.h"
|
||||
#include "vr4300/cp1.h"
|
||||
#include <setjmp.h>
|
||||
#include <limits.h>
|
||||
|
||||
cen64_cold int angrylion_rdp_init(struct cen64_device *device);
|
||||
cen64_cold static int device_debug_spin(struct cen64_device *device);
|
||||
|
@ -45,7 +46,7 @@ struct cen64_device *device_create(struct cen64_device *device,
|
|||
const struct save_file *eeprom, const struct save_file *sram,
|
||||
const struct save_file *flashram, struct is_viewer *is,
|
||||
const struct controller *controller,
|
||||
bool no_audio, bool no_video) {
|
||||
bool no_audio, bool no_video, bool profiling) {
|
||||
|
||||
// Initialize the bus.
|
||||
device->bus.ai = &device->ai;
|
||||
|
@ -117,7 +118,7 @@ struct cen64_device *device_create(struct cen64_device *device,
|
|||
}
|
||||
|
||||
// Initialize the VR4300.
|
||||
if (vr4300_init(&device->vr4300, &device->bus)) {
|
||||
if (vr4300_init(&device->vr4300, &device->bus, profiling)) {
|
||||
debug("create_device: Failed to initialize the VR4300.\n");
|
||||
return NULL;
|
||||
}
|
||||
|
@ -127,8 +128,30 @@ struct cen64_device *device_create(struct cen64_device *device,
|
|||
}
|
||||
|
||||
// Cleans up memory allocated for the device.
|
||||
void device_destroy(struct cen64_device *device) {
|
||||
void device_destroy(struct cen64_device *device, const char *cart_path) {
|
||||
rsp_destroy(&device->rsp);
|
||||
|
||||
// Save profiling data, if any
|
||||
if (cart_path && device->vr4300.profile_samples) {
|
||||
char path[PATH_MAX];
|
||||
snprintf(path, PATH_MAX, "%s.profile", cart_path);
|
||||
path[PATH_MAX - 1] = '\0';
|
||||
|
||||
FILE *f = fopen(path, "w");
|
||||
if (!f) {
|
||||
printf("Can't open %s\n", path);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t i;
|
||||
for (i = 0; i < 8 * 1024 * 1024; i++) {
|
||||
if (device->vr4300.profile_samples[i] < 10)
|
||||
continue;
|
||||
fprintf(f, "%x %lu\n", i + 0x80000000, device->vr4300.profile_samples[i]);
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
|
||||
// Called when we should (probably?) leave simulation.
|
||||
|
|
|
@ -54,7 +54,7 @@ struct cen64_device {
|
|||
bool running;
|
||||
};
|
||||
|
||||
cen64_cold void device_destroy(struct cen64_device *device);
|
||||
cen64_cold void device_destroy(struct cen64_device *device, const char *cart_path);
|
||||
cen64_cold struct cen64_device *device_create(struct cen64_device *device,
|
||||
const struct rom_file *ddipl, const struct dd_variant *dd_variant,
|
||||
const struct rom_file *ddrom,
|
||||
|
@ -62,7 +62,7 @@ cen64_cold struct cen64_device *device_create(struct cen64_device *device,
|
|||
const struct save_file *eeprom, const struct save_file *sram,
|
||||
const struct save_file *flashram, struct is_viewer *is,
|
||||
const struct controller *controller,
|
||||
bool no_audio, bool no_video);
|
||||
bool no_audio, bool no_video, bool profiling);
|
||||
|
||||
cen64_cold void device_exit(struct bus_controller *bus);
|
||||
cen64_cold void device_run(struct cen64_device *device);
|
||||
|
|
|
@ -30,6 +30,7 @@ const struct cen64_options default_cen64_options = {
|
|||
false, // console
|
||||
#endif
|
||||
false, // enable_debugger
|
||||
false, // enable_profiling
|
||||
false, // multithread
|
||||
false, // no_audio
|
||||
false, // no_video
|
||||
|
@ -59,6 +60,9 @@ int parse_options(struct cen64_options *options, int argc, const char *argv[]) {
|
|||
options->debugger_addr = "localhost:64646";
|
||||
}
|
||||
|
||||
else if (!strcmp(argv[i], "-profile"))
|
||||
options->enable_profiling = true;
|
||||
|
||||
else if (!strcmp(argv[i], "-multithread"))
|
||||
options->multithread = true;
|
||||
|
||||
|
@ -255,6 +259,7 @@ void print_command_line_usage(const char *invokation_string) {
|
|||
" -debug [addr][:port] : Starts the debugger on interface:port.\n"
|
||||
" By default, CEN64 uses localhost:64646.\n"
|
||||
" NOTE: the debugger is not implemented yet.\n"
|
||||
" -profile : Profile the ROM (cpu-side).\n"
|
||||
" -multithread : Run in a threaded (but quasi-accurate) mode.\n"
|
||||
" : This mode cannot be run with the debugger.\n"
|
||||
" -ddipl <path> : Path to the 64DD IPL ROM (enables 64DD mode).\n"
|
||||
|
|
|
@ -32,6 +32,7 @@ struct cen64_options {
|
|||
#endif
|
||||
|
||||
bool enable_debugger;
|
||||
bool enable_profiling;
|
||||
bool multithread;
|
||||
bool no_audio;
|
||||
bool no_video;
|
||||
|
|
142
util/cen64-profile2callgrind.cpp
Normal file
142
util/cen64-profile2callgrind.cpp
Normal file
|
@ -0,0 +1,142 @@
|
|||
//
|
||||
// cen64-profile2callgrind: Convert a cen64 profile file into a callgrind one.
|
||||
//
|
||||
// CEN64: Cycle-Accurate Nintendo 64 Emulator.
|
||||
// Copyright (C) 2018 Lauri Kasanen
|
||||
//
|
||||
// This file is subject to the terms and conditions defined in
|
||||
// 'LICENSE', which is part of this source code package.
|
||||
//
|
||||
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
static void die(const char fmt[], ...) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
|
||||
string s;
|
||||
map<uint32_t, string> funcs;
|
||||
char buf[PATH_MAX];
|
||||
|
||||
if (argc != 3) {
|
||||
die("Usage: %s my.z64.profile my.elf\n", argv[0]);
|
||||
}
|
||||
|
||||
s = "nm ";
|
||||
s += argv[2];
|
||||
s += " | grep -i \" t \"";
|
||||
|
||||
FILE *f = popen(s.c_str(), "r");
|
||||
if (!f) die("Can't get functions\n");
|
||||
|
||||
// Add the bootloader
|
||||
funcs[0x80000000] = "PIF bootloader";
|
||||
while (fgets(buf, PATH_MAX, f)) {
|
||||
uint32_t tmp;
|
||||
if (sscanf(buf, "%x", &tmp) != 1)
|
||||
die("Failed getting functions\n");
|
||||
|
||||
char *ptr = strrchr(buf, ' ');
|
||||
if (!ptr)
|
||||
die("Failed getting functions\n");
|
||||
ptr++;
|
||||
|
||||
uint32_t len = strlen(ptr);
|
||||
if (ptr[len - 1] == '\n')
|
||||
ptr[len - 1] = '\0';
|
||||
|
||||
// Skip internal/libc magic parts
|
||||
if (ptr[0] == '_' && ptr[1] == '_')
|
||||
continue;
|
||||
|
||||
funcs[tmp] = ptr;
|
||||
}
|
||||
|
||||
pclose(f);
|
||||
|
||||
strcpy(buf, argv[1]);
|
||||
char *ptr = strstr(buf, ".profile");
|
||||
if (ptr) {
|
||||
*ptr = '\0';
|
||||
}
|
||||
strcat(buf, ".callgrind");
|
||||
|
||||
FILE *out = fopen(buf, "w");
|
||||
if (!out) die("Can't open '%s' for output\n", buf);
|
||||
|
||||
fprintf(out, "# callgrind format\n");
|
||||
fprintf(out, "cmd: %s\n", argv[2]);
|
||||
fprintf(out, "events: instructions\n\n");
|
||||
|
||||
fprintf(out, "ob=%s\n\n", argv[2]);
|
||||
|
||||
uint64_t summary = 0;
|
||||
|
||||
// We have a list of functions, now turn the samples into C line info
|
||||
f = fopen(argv[1], "r");
|
||||
if (!f) die("Can't open profile file\n");
|
||||
|
||||
while (fgets(buf, PATH_MAX, f)) {
|
||||
uint32_t addr;
|
||||
uint64_t num;
|
||||
if (sscanf(buf, "%x %lu", &addr, &num) != 2)
|
||||
die("Malformed profile file\n");
|
||||
|
||||
summary += num;
|
||||
|
||||
char *ptr = strchr(buf, ' ');
|
||||
*ptr = '\0';
|
||||
|
||||
s = "addr2line -s -e ";
|
||||
s += argv[2];
|
||||
s += " ";
|
||||
s += buf;
|
||||
|
||||
char linebuf[PATH_MAX];
|
||||
FILE *p = popen(s.c_str(), "r");
|
||||
if (!fgets(linebuf, PATH_MAX, p))
|
||||
die("Failed getting addr\n");
|
||||
pclose(p);
|
||||
|
||||
ptr = strchr(linebuf, ':');
|
||||
*ptr = '\0';
|
||||
ptr++;
|
||||
|
||||
uint32_t line = 0;
|
||||
if (*ptr != '?' && sscanf(ptr, "%u", &line) != 1)
|
||||
die("Failed getting addr\n");
|
||||
|
||||
// Okay, we have everything for this sample. Put it out
|
||||
fprintf(out, "fl=%s\n", linebuf);
|
||||
map<uint32_t, string>::const_iterator it = funcs.lower_bound(addr);
|
||||
it--;
|
||||
fprintf(out, "fn=%s\n", it->second.c_str());
|
||||
|
||||
fprintf(out, "%u %lu\n\n", line, num);
|
||||
}
|
||||
|
||||
fprintf(out, "totals: %lu\n", summary);
|
||||
|
||||
fclose(f);
|
||||
fclose(out);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@ static void vr4300_connect_bus(struct vr4300 *vr4300,
|
|||
}
|
||||
|
||||
// Initializes the VR4300 component.
|
||||
int vr4300_init(struct vr4300 *vr4300, struct bus_controller *bus) {
|
||||
int vr4300_init(struct vr4300 *vr4300, struct bus_controller *bus, bool profiling) {
|
||||
vr4300_connect_bus(vr4300, bus);
|
||||
|
||||
vr4300_cp0_init(vr4300);
|
||||
|
@ -45,6 +45,12 @@ int vr4300_init(struct vr4300 *vr4300, struct bus_controller *bus) {
|
|||
// MESS uses this version, so we will too?
|
||||
vr4300->mi_regs[MI_VERSION_REG] = 0x01010101;
|
||||
vr4300->mi_regs[MI_INIT_MODE_REG] = 0x80;
|
||||
|
||||
if (profiling)
|
||||
vr4300->profile_samples = calloc(8 * 1024 * 1024, sizeof(uint64_t));
|
||||
else
|
||||
vr4300->profile_samples = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -102,6 +102,7 @@ struct vr4300 {
|
|||
struct vr4300_dcache dcache;
|
||||
struct vr4300_icache icache;
|
||||
|
||||
uint64_t *profile_samples;
|
||||
};
|
||||
|
||||
struct vr4300_stats {
|
||||
|
@ -111,7 +112,7 @@ struct vr4300_stats {
|
|||
unsigned long opcode_counts[NUM_VR4300_OPCODES];
|
||||
};
|
||||
|
||||
cen64_cold int vr4300_init(struct vr4300 *vr4300, struct bus_controller *bus);
|
||||
cen64_cold int vr4300_init(struct vr4300 *vr4300, struct bus_controller *bus, bool profiling);
|
||||
cen64_cold void vr4300_print_summary(struct vr4300_stats *stats);
|
||||
|
||||
cen64_flatten cen64_hot void vr4300_cycle_(struct vr4300 *vr4300);
|
||||
|
|
|
@ -185,6 +185,12 @@ static int vr4300_ex_stage(struct vr4300 *vr4300) {
|
|||
vr4300_opcode_mnemonics[rfex_latch->opcode.id]);
|
||||
#endif
|
||||
|
||||
if (vr4300->profile_samples) {
|
||||
uint32_t idx = rfex_latch->common.pc - 0x80000000;
|
||||
idx &= (8 * 1024 * 1024) - 1;
|
||||
vr4300->profile_samples[idx]++;
|
||||
}
|
||||
|
||||
exdc_latch->dest = VR4300_REGISTER_R0;
|
||||
exdc_latch->request.type = VR4300_BUS_REQUEST_NONE;
|
||||
return vr4300_function_table[rfex_latch->opcode.id](
|
||||
|
|
Loading…
Reference in a new issue