ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
emu_prof.c
Go to the documentation of this file.
1
16
17#include "emu_prof.h"
18
19#include <stdio.h>
20#include <stdlib.h>
21#include <string.h>
22#include <time.h>
23
24#include "emu_elf.h"
26
28static const double s_nsec_per_sec = 1.0e9;
29
31double board_now_s(void)
32{
33 struct timespec ts = {};
34 (void)clock_gettime(CLOCK_MONOTONIC, &ts);
35 return (double)ts.tv_sec + ((double)ts.tv_nsec / s_nsec_per_sec);
36}
37
38/* ===========================================================================
39 * Firmware profiler (RA8_EMU_PROFILE). Two modes, bucketed by ELF FUNC symbol:
40 * =1 wall-time sample -- charge each chunk's wall time to its start PC.
41 * Cheap (no per-instruction cost); a flat % list of the dominant cost.
42 * =full per-instruction -- a code hook tallies every instruction + call entry
43 * AND reconstructs the live call chain (see below), so the run end emits
44 * an Ozone-style breakdown: a boot timeline, an inclusive/self table,
45 * and a speedscope flamechart file. Accurate but ~10x slower; off by
46 * default. The run auto-stops once boot settles into the idle frame
47 * loop, so =full profiles boot work rather than the idle tail.
48 * ===========================================================================
49 */
50enum : uint32_t {
53};
54typedef struct {
55 uint32_t lo;
56 uint32_t hi;
57 uint64_t name_offset;
58 double secs;
59 uint64_t insns;
60 uint64_t calls;
64static uint32_t s_prof_n = 0U;
65static double s_prof_total_s = 0.0;
66static uint64_t s_prof_total_i = 0U;
68
69/* ---------------------------------------------------------------------------
70 * Ozone-style call-stack tracing (insn mode only). On top of the per-function
71 * tally above, reconstruct the live call chain straight from the PC stream: a
72 * fresh function entry that is not already on the chain is a call (push), and
73 * re-entering a function already deeper on the chain is a return (pop down to
74 * it). NASA Rule 1 bans recursion in this firmware, so a function appears at
75 * most once on the chain and the "already on the chain" test is unambiguous.
76 * The chain is sampled at a fixed instruction cadence into a bounded,
77 * chronological store and written out as a speedscope "sampled" profile -- open
78 * ra8_emulator_profile.speedscope.json at https://speedscope.app for the
79 * time-ordered flamechart ("what ran when", the Ozone timeline) plus the
80 * sandwich view (self vs total per function). WFI idle naturally weighs ~zero
81 * because a halted core retires no instructions, so the picture is boot work,
82 * not the idle frame loop. ===============================================
83 */
84enum : uint32_t {
88};
89static uint16_t s_pstk[k_prof_max_depth];
90static uint32_t s_pstk_n = 0U;
93static uint32_t s_samp_w[k_prof_max_samples];
94static uint32_t s_samp_n = 0U;
95static uint64_t s_samp_every = (uint64_t)k_prof_samp_every;
96static uint64_t s_samp_acc = 0U;
97static uint32_t s_prof_stop_pc = 0U;
98static bool s_prof_stop_hit = false;
99static uint64_t s_incl[k_prof_max_syms];
100static uint64_t s_self[k_prof_max_syms];
101
114RA8_INTERNAL static int internal_prof_cmp(const void* a, const void* b)
115{
116 const uint32_t la = ((const prof_sym_t*)a)->lo;
117 const uint32_t lb = ((const prof_sym_t*)b)->lo;
118 if (la < lb) {
119 return -1;
120 }
121 if (la > lb) {
122 return 1;
123 }
124 return 0;
125}
126
142RA8_INTERNAL static bool internal_prof_symbol(const emu_elf_symbol_t* symbol, void* ctx)
143{
144 (void)ctx;
145 if (((symbol->info & (uint8_t)k_elf_st_type_mask) != 2U) || (symbol->size == 0U) ||
146 (symbol->name_offset == 0U)) {
147 return true;
148 }
149 if (s_prof_n >= (uint32_t)k_prof_max_syms) {
150 return false;
151 }
152 const uint32_t lo = symbol->value & ~1U;
154 (prof_sym_t){.lo = lo, .hi = lo + symbol->size, .name_offset = symbol->name_offset};
155 s_prof_n++;
156 return true;
157}
158
161{
162 const char* mode = getenv("RA8_EMU_PROFILE");
163 if (mode == nullptr) {
165 return;
166 }
168 ((strcmp(mode, "full") == 0) || (strcmp(mode, "insn") == 0)) ? k_prof_insn : k_prof_wall;
169 s_prof_n = 0U;
170 s_prof_elf = elf;
171 (void)elf_foreach_symbol(elf, internal_prof_symbol, nullptr);
172 qsort(s_prof, (size_t)s_prof_n, sizeof(s_prof[0]), internal_prof_cmp);
173 (void)priv_emu_io_errf(" [profile] %s; %u FUNC symbols\n",
174 (s_prof_mode == k_prof_insn) ? "per-instruction (exact, slow)"
175 : "wall-time sample",
176 (unsigned)s_prof_n);
177}
178
190RA8_INTERNAL static uint32_t internal_prof_find(uint32_t pc)
191{
192 uint32_t lo = 0U;
193 uint32_t hi = s_prof_n;
194 while (lo < hi) {
195 const uint32_t mid = lo + ((hi - lo) / 2U);
196 if (s_prof[mid].lo <= pc) {
197 lo = mid + 1U;
198 } else {
199 hi = mid;
200 }
201 }
202 if (lo == 0U) {
203 return s_prof_n;
204 }
205 const uint32_t idx = lo - 1U;
206 return (pc < s_prof[idx].hi) ? idx : s_prof_n;
207}
208
210void prof_add(uint32_t pc, double dt)
211{
212 if (s_prof_mode != k_prof_wall) {
213 return;
214 }
215 s_prof_total_s += dt;
216 const uint32_t idx = internal_prof_find(pc);
217 if (idx < s_prof_n) {
218 s_prof[idx].secs += dt;
219 }
220}
221
231{
232 uint32_t dst = 0U;
233 for (uint32_t i = 0U; i < s_samp_n; i += 2U) {
234 const uint32_t w2 = ((i + 1U) < s_samp_n) ? s_samp_w[i + 1U] : 0U;
235 if (dst != i) {
236 (void)memcpy(s_samp[dst], s_samp[i], (size_t)s_samp_d[i] * sizeof(uint16_t));
237 s_samp_d[dst] = s_samp_d[i];
238 }
239 s_samp_w[dst] = s_samp_w[i] + w2; /* merged time keeps the total exact. */
240 dst++;
241 }
242 s_samp_n = dst;
243 s_samp_every *= 2U; /* coarser cadence keeps the next fill the same span. */
244}
245
255RA8_INTERNAL static void internal_prof_sample(uint32_t weight)
256{
257 if (s_samp_n >= (uint32_t)k_prof_max_samples) {
259 }
260 uint32_t d = s_pstk_n;
261 if (d > (uint32_t)k_prof_max_depth) {
262 d = (uint32_t)k_prof_max_depth;
263 }
264 for (uint32_t i = 0U; i < d; i++) {
265 s_samp[s_samp_n][i] = s_pstk[i];
266 }
267 s_samp_d[s_samp_n] = (uint8_t)d;
268 s_samp_w[s_samp_n] = weight;
269 s_samp_n++;
270}
271
282{
283 if (f >= s_prof_n) {
284 return; /* unknown region -- keep the current leaf (it gets the self time). */
285 }
286 if ((s_pstk_n > 0U) && (s_pstk[s_pstk_n - 1U] == (uint16_t)f)) {
287 return; /* still in the same function -- no call/return transition. */
288 }
289 for (uint32_t i = s_pstk_n; i > 0U; i--) {
290 if (s_pstk[i - 1U] == (uint16_t)f) {
291 s_pstk_n = i; /* returned to a frame already on the chain -- unwind to it. */
292 return;
293 }
294 }
295 if (s_pstk_n < (uint32_t)k_prof_max_depth) {
296 s_pstk[s_pstk_n] = (uint16_t)f; /* a fresh call -- push it. */
297 s_pstk_n++;
298 }
299}
300
313RA8_INTERNAL static void
314internal_prof_insn_hook(uc_engine* uc, uint64_t address, uint32_t size, void* user)
315{
316 (void)size;
317 (void)user;
319 const uint32_t idx = internal_prof_find((uint32_t)address);
320 if (idx < s_prof_n) {
321 s_prof[idx].insns++;
322 if ((uint32_t)address == s_prof[idx].lo) {
323 s_prof[idx].calls++; /* PC at the entry point -> a fresh call (approx). */
324 }
325 }
327 s_samp_acc++;
328 if (s_samp_acc >= s_samp_every) {
330 s_samp_acc = 0U;
331 }
332 if ((s_prof_stop_pc != 0U) && ((uint32_t)address == s_prof_stop_pc)) {
333 s_prof_stop_hit = true; /* RA8_EMU_STOP_PC reached -- end the run cleanly. */
334 (void)uc_emu_stop(uc);
335 }
336}
337
353{
354 return result.status == k_emu_io_ok;
355}
356
358typedef struct {
359 int fd;
360 char escape;
362 bool ok;
364
381RA8_INTERNAL static bool internal_prof_name_chunk(const char* bytes, size_t length, void* opaque)
382{
383 prof_name_sink_t* const sink = (prof_name_sink_t*)opaque;
384 if ((sink->escape == '\0') && sink->error_sink) {
385 sink->ok = internal_prof_io_ok(priv_emu_io_err_bytes(bytes, length));
386 return sink->ok;
387 }
388 for (size_t index = 0U; (index < length) && sink->ok; index++) {
389 if ((sink->escape != '\0') && (bytes[index] == sink->escape || bytes[index] == '\\')) {
390 sink->ok = internal_prof_io_ok(priv_emu_io_file_char(sink->fd, '\\'));
391 }
392 if (sink->ok) {
393 sink->ok = internal_prof_io_ok(priv_emu_io_file_char(sink->fd, bytes[index]));
394 }
395 }
396 return sink->ok;
397}
398
416RA8_INTERNAL static bool internal_prof_name(uint32_t symbol, int fd, char escape, bool error_sink)
417{
418 if ((symbol >= s_prof_n) || (s_prof_elf == nullptr)) {
419 return false;
420 }
421 prof_name_sink_t sink = {.fd = fd, .escape = escape, .error_sink = error_sink, .ok = true};
423 s_prof[symbol].name_offset,
425 &sink) &&
426 sink.ok;
427}
428
444{
445 bool ok = true;
446 for (uint32_t i = 0U; i < s_prof_n; i++) {
448 priv_emu_io_file_text(fd, (i == 0U) ? "{\"name\":\"" : ",{\"name\":\"")) &&
449 ok;
450 ok = internal_prof_name(i, fd, '"', false) && ok;
451 ok = internal_prof_io_ok(priv_emu_io_file_text(fd, "\"}")) && ok;
452 }
453 return ok;
454}
455
474{
475 bool ok = true;
476 for (uint32_t i = 0U; i < s_samp_n; i++) {
477 ok = internal_prof_io_ok(priv_emu_io_file_char(fd, (i == 0U) ? '[' : ',')) && ok;
478 if (i != 0U) {
479 ok = internal_prof_io_ok(priv_emu_io_file_char(fd, '[')) && ok;
480 }
481 for (uint8_t j = 0U; j < s_samp_d[i]; j++) {
483 priv_emu_io_filef(fd, (j == 0U) ? "%u" : ",%u", (unsigned)s_samp[i][j])) &&
484 ok;
485 }
486 ok = internal_prof_io_ok(priv_emu_io_file_char(fd, ']')) && ok;
487 }
488 return ok;
489}
490
509{
510 bool ok = true;
511 for (uint32_t i = 0U; i < s_samp_n; i++) {
512 ok =
513 internal_prof_io_ok(priv_emu_io_filef(fd, (i == 0U) ? "%u" : ",%u", (unsigned)s_samp_w[i])) &&
514 ok;
515 }
516 return ok;
517}
518
540{
541 bool ok = true;
542 for (uint32_t i = 0U; i < s_prof_n; i++) {
543 ok = internal_prof_io_ok(priv_emu_io_file_text(fd, (i == 0U) ? "'" : ",'")) && ok;
544 ok = internal_prof_name(i, fd, '\'', false) && ok;
545 ok = internal_prof_io_ok(priv_emu_io_file_char(fd, '\'')) && ok;
546 }
547 return ok;
548}
549
559RA8_INTERNAL static void internal_prof_write_speedscope(const char* path)
560{
561 if ((s_samp_n == 0U) || (s_prof_n == 0U)) {
562 return;
563 }
564 emu_io_txn_t txn = {.fd = -1};
565 if (priv_emu_io_txn_begin(path, &txn).status != k_emu_io_ok) {
566 return;
567 }
568 uint64_t total = 0U;
569 for (uint32_t i = 0U; i < s_samp_n; i++) {
570 total += s_samp_w[i];
571 }
572 bool ok = internal_prof_io_ok(
574 "{\"$schema\":\"https://www.speedscope.app/file-format-schema.json\",\n"
575 " \"name\":\"ra8_emulator boot\",\"activeProfileIndex\":0,\n"
576 " \"shared\":{\"frames\":["));
577 ok = internal_prof_json_frames(txn.fd) && ok;
579 txn.fd,
580 "]},\n"
581 " \"profiles\":[{\"type\":\"sampled\",\"name\":\"boot\",\"unit\":\"none\",\n"
582 " \"startValue\":0,\"endValue\":%llu,\n"
583 " \"samples\":[",
584 (unsigned long long)total)) &&
585 ok;
586 ok = internal_prof_json_samples(txn.fd) && ok;
587 ok = internal_prof_io_ok(priv_emu_io_file_text(txn.fd, "],\n \"weights\":[")) && ok;
588 ok = internal_prof_json_weights(txn.fd) && ok;
589 ok = internal_prof_io_ok(priv_emu_io_file_text(txn.fd, "]}]}\n")) && ok;
590 if (!ok || (priv_emu_io_txn_commit(&txn).status != k_emu_io_ok)) {
592 }
593}
594
595/* Self-contained flamechart viewer markup. The page embeds the profile arrays
596 * (written just before this) and renders a time-ordered flame chart on a canvas
597 * -- the Ozone timeline, but as a local file that opens in any browser with no
598 * upload and no external site. Single-quoted HTML/JS strings keep the C literal
599 * free of escapes; pure 7-bit ASCII. */
600static const char s_k_prof_html_head[] =
601 "<!doctype html><html><head><meta charset='utf-8'><title>ra8_emulator profile</title>\n"
602 "<style>\n"
603 "body{margin:0;font:12px Menlo,monospace;background:#1e1e1e;color:#ddd}\n"
604 "#bar{padding:7px 10px;background:#2a2a2a;border-bottom:1px solid #444}\n"
605 "#bar b{color:#fff}#bar button,#bar input{font:11px monospace;margin-left:10px;"
606 "background:#3a3a3a;color:#ddd;border:1px solid #555;padding:2px 6px}\n"
607 "#tip{position:fixed;pointer-events:none;background:#000;color:#fff;padding:5px 7px;"
608 "border:1px solid #888;display:none;white-space:nowrap;z-index:9;font:11px monospace}\n"
609 "canvas{display:block;cursor:crosshair}\n"
610 "</style></head><body>\n"
611 "<div id='bar'><b id='title'></b><span id='info'></span>"
612 "<button onclick='resetView()'>Reset zoom</button>"
613 "search:<input id='q' size='18' oninput='onSearch()'></div>\n"
614 "<canvas id='fc'></canvas><div id='tip'></div>\n<script>\n";
615
616static const char s_k_prof_html_js[] =
617 "var cv=document.getElementById('fc'),ctx=cv.getContext('2d'),tip=document.getElementById('tip');\n"
618 "document.getElementById('title').textContent=TITLE;\n"
619 "var ROW=18,total=0,i;for(i=0;i<WEIGHTS.length;i++)total+=WEIGHTS[i];\n"
620 "var maxd=0;for(i=0;i<SAMPLES.length;i++)if(SAMPLES[i].length>maxd)maxd=SAMPLES[i].length;\n"
621 "var rects=[],incl={},self={};\n"
622 "for(var d=0;d<maxd;d++){var cum=0,rs=0,rf=-1,op=false;\n"
623 " for(i=0;i<SAMPLES.length;i++){var ff=d<SAMPLES[i].length?SAMPLES[i][d]:-1;\n"
624 " if(!(op&&ff===rf)){if(op&&rf>=0)rects.push({d:d,a:rs,b:cum,f:rf});rs=cum;rf=ff;op=true;}\n"
625 " cum+=WEIGHTS[i];}\n"
626 " if(op&&rf>=0)rects.push({d:d,a:rs,b:cum,f:rf});}\n"
627 "for(i=0;i<SAMPLES.length;i++){var s=SAMPLES[i],w=WEIGHTS[i];\n"
628 " for(var j=0;j<s.length;j++)incl[s[j]]=(incl[s[j]]||0)+w;\n"
629 " if(s.length)self[s[s.length-1]]=(self[s[s.length-1]]||0)+w;}\n"
630 "var vx0=0,vx1=total,q='';\n"
631 "function col(fi){var n=FRAMES[fi],h=0,k;for(k=0;k<n.length;k++)h=(h*31+n.charCodeAt(k))&0xffffff;\n"
632 " var lit=(q&&n.toLowerCase().indexOf(q)>=0);return 'hsl('+(h%359)+','+(lit?'90%':'48%')+','+(lit?'62%':'44%')+')';}\n"
633 "function resize(){cv.width=window.innerWidth;cv.height=Math.max(maxd*ROW+4,160);draw();}\n"
634 "function draw(){ctx.clearRect(0,0,cv.width,cv.height);var span=vx1-vx0;if(span<=0)return;\n"
635 " ctx.font='11px monospace';ctx.textBaseline='middle';\n"
636 " for(var r=0;r<rects.length;r++){var R=rects[r];if(R.b<=vx0||R.a>=vx1)continue;\n"
637 " var p0=(R.a-vx0)/span*cv.width,p1=(R.b-vx0)/span*cv.width,w=p1-p0;if(w<0.4)continue;\n"
638 " var y=R.d*ROW;ctx.fillStyle=col(R.f);ctx.fillRect(p0,y,Math.max(w-0.6,0.5),ROW-1);\n"
639 " if(w>34){ctx.fillStyle='#111';ctx.fillText(FRAMES[R.f],p0+3,y+ROW/2,w-6);}}}\n"
640 "function pick(mx,my){var d=Math.floor(my/ROW),span=vx1-vx0,wx=vx0+mx/cv.width*span,r;\n"
641 " for(r=0;r<rects.length;r++){var R=rects[r];if(R.d===d&&wx>=R.a&&wx<R.b)return R;}return null;}\n"
642 "cv.onmousemove=function(e){var R=pick(e.offsetX,e.offsetY);if(!R){tip.style.display='none';return;}\n"
643 " var n=FRAMES[R.f],to=incl[R.f]||0,se=self[R.f]||0;\n"
644 " tip.innerHTML=n+'<br>this block: '+((R.b-R.a)/total*100).toFixed(2)+'% ('+(R.b-R.a)+' insns)'+\n"
645 " '<br>total '+(to/total*100).toFixed(2)+'% self '+(se/total*100).toFixed(2)+'%';\n"
646 " tip.style.display='block';tip.style.left=(e.clientX+14)+'priv_px';tip.style.top=(e.clientY+14)+'priv_px';};\n"
647 "cv.onmouseleave=function(){tip.style.display='none';};\n"
648 "cv.onclick=function(e){var R=pick(e.offsetX,e.offsetY);if(R){vx0=R.a;vx1=R.b;draw();}};\n"
649 "function resetView(){vx0=0;vx1=total;draw();}\n"
650 "function onSearch(){q=document.getElementById('q').value.toLowerCase();draw();}\n"
651 "document.getElementById('info').textContent=' | '+SAMPLES.length+' samples, '+total+\n"
652 " ' insns (hover for self/total, click a block to zoom, Reset to zoom out)';\n"
653 "window.onresize=resize;resize();\n";
654
665RA8_INTERNAL static void internal_prof_write_html(const char* path, uint64_t total)
666{
667 if ((s_samp_n == 0U) || (s_prof_n == 0U)) {
668 return;
669 }
670 emu_io_txn_t txn = {.fd = -1};
671 if (priv_emu_io_txn_begin(path, &txn).status != k_emu_io_ok) {
672 return;
673 }
675 ok = internal_prof_io_ok(priv_emu_io_file_text(txn.fd, "var FRAMES=[")) && ok;
676 ok = internal_prof_js_frames(txn.fd) && ok;
677 ok = internal_prof_io_ok(priv_emu_io_file_text(txn.fd, "];\n")) && ok;
678 /* SAMPLES / WEIGHTS are plain integer arrays, so the JSON the speedscope
679 * writer emits is already valid JavaScript -- the same two helpers serve. */
680 ok = internal_prof_io_ok(priv_emu_io_file_text(txn.fd, "var SAMPLES=[")) && ok;
681 ok = internal_prof_json_samples(txn.fd) && ok;
682 ok = internal_prof_io_ok(priv_emu_io_file_text(txn.fd, "];\nvar WEIGHTS=[")) && ok;
683 ok = internal_prof_json_weights(txn.fd) && ok;
686 "];\nvar TITLE='ra8_emulator flamechart -- %llu insns, %u samples';\n",
687 (unsigned long long)total,
688 (unsigned)s_samp_n)) &&
689 ok;
691 ok = internal_prof_io_ok(priv_emu_io_file_text(txn.fd, "</script></body></html>\n")) && ok;
692 if (!ok || (priv_emu_io_txn_commit(&txn).status != k_emu_io_ok)) {
694 }
695}
696
698static const double s_percent_scale = 100.0;
699
701typedef enum : uint32_t {
702 k_no_fn = 0xFFFFFFFFU,
707
719{
720 /* Inclusive (anywhere on the chain) + self (leaf) weights, from the samples.
721 * No recursion (NASA Rule 1) -> each function appears at most once per sample,
722 * so a straight per-frame add needs no dedup. */
723 uint64_t total = 0U;
724 for (uint32_t i = 0U; i < s_prof_n; i++) {
725 s_incl[i] = 0U;
726 s_self[i] = 0U;
727 }
728 for (uint32_t i = 0U; i < s_samp_n; i++) {
729 const uint32_t w = s_samp_w[i];
730 const uint8_t d = s_samp_d[i];
731 total += w;
732 for (uint8_t j = 0U; j < d; j++) {
733 s_incl[s_samp[i][j]] += w;
734 }
735 if (d > 0U) {
736 s_self[s_samp[i][d - 1U]] += w;
737 }
738 }
739 return total;
740}
741
757RA8_INTERNAL static void
758internal_prof_print_phase(uint32_t segfn, uint64_t cum, uint64_t segw, uint64_t total)
759{
760 (void)priv_emu_io_errf(" %5.1f%% +%4.1f%% ",
761 s_percent_scale * (double)cum / (double)total,
762 s_percent_scale * (double)segw / (double)total);
763 if (segfn < s_prof_n) {
764 (void)internal_prof_name(segfn, -1, '\0', true);
765 } else {
766 (void)priv_emu_io_err_text("?");
767 }
768 (void)priv_emu_io_err_text("\n");
769}
770
781{
782 /* Phase timeline: collapse each sample's chain to a fixed shallow depth (the
783 * major subsystem under main) and print each contiguous run as a boot phase,
784 * so the terminal shows "what ran when" even without opening speedscope. */
785 (void)priv_emu_io_errf(" [profile] boot timeline (phase = call depth %u; start%% .. width%%):\n",
786 (unsigned)k_phase_depth);
787 uint64_t cum = 0U;
788 uint64_t segw = 0U;
789 uint32_t segfn = (uint32_t)k_no_fn;
790 uint32_t lines = 0U;
791 for (uint32_t i = 0U; i <= s_samp_n; i++) {
792 uint32_t fn = (uint32_t)k_no_fn;
793 if (i < s_samp_n) {
794 const uint8_t d = s_samp_d[i];
795 if (d > 0U) {
796 const uint8_t pd = ((uint32_t)(d - 1U) < (uint32_t)k_phase_depth) ? (uint8_t)(d - 1U)
797 : (uint8_t)k_phase_depth;
798 fn = s_samp[i][pd];
799 }
800 }
801 if ((i == s_samp_n) || (fn != segfn)) {
802 const bool show = (segfn != (uint32_t)k_no_fn) &&
803 (((uint32_t)k_phase_pct_x1 * segw) >= total) &&
804 (lines < (uint32_t)k_phase_lines);
805 if (show) {
806 internal_prof_print_phase(segfn, cum, segw, total);
807 lines++;
808 }
809 cum += segw;
810 segw = 0U;
811 segfn = fn;
812 }
813 if (i < s_samp_n) {
814 segw += s_samp_w[i];
815 }
816 }
817}
818
830RA8_INTERNAL static void
831internal_prof_print_incl_self_table(const char* html, const char* out, uint64_t total)
832{
833 /* Inclusive/self table -- the "why is it slow" view (sorted by inclusive). */
834 (void)priv_emu_io_errf(" [profile] flamechart GUI -> %s (interactive: hover/zoom/search)\n"
835 " [profile] %u samples over %llu insns (also %s for speedscope.app)\n"
836 " self%% total%% function\n",
837 html,
838 (unsigned)s_samp_n,
839 (unsigned long long)total,
840 out);
841 for (uint32_t k = 0U; k < (uint32_t)k_prof_top_n; k++) {
842 uint32_t best = s_prof_n;
843 uint64_t bestv = 0U;
844 for (uint32_t i = 0U; i < s_prof_n; i++) {
845 if (s_incl[i] > bestv) {
846 bestv = s_incl[i];
847 best = i;
848 }
849 }
850 if ((best == s_prof_n) || (bestv == 0U)) {
851 break;
852 }
853 (void)priv_emu_io_errf(" %8.2f%% %7.2f%% ",
854 s_percent_scale * (double)s_self[best] / (double)total,
855 s_percent_scale * (double)s_incl[best] / (double)total);
856 (void)internal_prof_name(best, -1, '\0', true);
857 (void)priv_emu_io_err_text("\n");
858 s_incl[best] = 0U; /* consume so the next pick is the runner-up. */
859 }
860}
861
871{
872 const char* out = getenv("RA8_EMU_PROFILE_OUT");
873 if ((out == nullptr) || (out[0] == '\0')) {
874 out = "ra8_emulator_profile.speedscope.json";
875 }
876 const char* html = getenv("RA8_EMU_PROFILE_HTML");
877 if ((html == nullptr) || (html[0] == '\0')) {
878 html = "ra8_emulator_profile.html";
879 }
881
882 const uint64_t total = internal_prof_accumulate_incl_self();
883 if (total == 0U) {
884 return;
885 }
886 internal_prof_write_html(html, total); /* self-contained local GUI flamechart. */
888 internal_prof_print_incl_self_table(html, out, total);
889}
890
892void prof_report(void)
893{
894 const bool insn = (s_prof_mode == k_prof_insn);
895 const double tot = insn ? (double)s_prof_total_i : s_prof_total_s;
896 if ((s_prof_mode == k_prof_off) || (tot <= 0.0)) {
897 return;
898 }
899 if (insn) {
900 (void)priv_emu_io_errf(" [profile] %llu instructions; hottest (by instruction count):\n"
901 " %%insn instructions calls function\n",
902 (unsigned long long)s_prof_total_i);
903 } else {
904 (void)priv_emu_io_errf(" [profile] %.2fs wall sampled; hottest (by wall time):\n", tot);
905 }
906 for (uint32_t k = 0U; k < (uint32_t)k_prof_top_n; k++) {
907 uint32_t best = s_prof_n;
908 double bestv = 0.0;
909 for (uint32_t i = 0U; i < s_prof_n; i++) {
910 const double v = insn ? (double)s_prof[i].insns : s_prof[i].secs;
911 if (v > bestv) {
912 bestv = v;
913 best = i;
914 }
915 }
916 if ((best == s_prof_n) || (bestv <= 0.0)) {
917 break;
918 }
919 if (insn) {
920 (void)priv_emu_io_errf(" %6.2f%% %15llu %10llu ",
921 s_percent_scale * bestv / tot,
922 (unsigned long long)s_prof[best].insns,
923 (unsigned long long)s_prof[best].calls);
924 (void)internal_prof_name(best, -1, '\0', true);
925 (void)priv_emu_io_err_text("\n");
926 s_prof[best].insns = 0U;
927 } else {
928 (void)priv_emu_io_errf(" %6.2f%% %8.2fs ", s_percent_scale * bestv / tot, bestv);
929 (void)internal_prof_name(best, -1, '\0', true);
930 (void)priv_emu_io_err_text("\n");
931 s_prof[best].secs = 0.0;
932 }
933 }
934 if (insn && (s_samp_n > 0U)) {
935 internal_prof_report_flamechart(); /* speedscope export + inclusive/self + timeline. */
936 }
937}
938
940void emu_prof_install(uc_engine* uc)
941{
942 if (s_prof_mode == k_prof_insn) {
943 static uc_hook s_h_prof;
944 (void)uc_hook_add(uc, &s_h_prof, UC_HOOK_CODE, (void*)internal_prof_insn_hook, nullptr, 1, 0);
945 }
946}
947
950{
951 return s_prof_mode;
952}
953
956{
957 return s_prof_total_i;
958}
959
961void emu_prof_set_stop_pc(uint32_t pc)
962{
963 s_prof_stop_pc = pc;
964}
965
968{
969 return s_prof_stop_hit;
970}
ELF32 image services for the board emulator (load / symbols / vectors).
bool elf_string_foreach(const emu_elf_source_t *elf, uint64_t offset, emu_elf_string_fn fn, void *ctx)
Stream one NUL-terminated ELF string through bounded stack chunks.
uint32_t elf_foreach_symbol(const emu_elf_source_t *elf, emu_elf_symbol_fn fn, void *ctx)
Walk every symbol in every usable SHT_SYMTAB section.
@ k_elf_st_type_mask
Low nibble of st_info is the type.
Definition emu_elf.h:70
Bounded raw-descriptor I/O seam for the RA8 emulator.
emu_io_result_t priv_emu_io_err_text(const char *text)
Write a NUL-terminated literal to the injected error descriptor.
@ k_emu_io_ok
The complete operation succeeded.
emu_io_result_t priv_emu_io_errf(const char *format,...)
Format bounded text and write it to the injected error descriptor.
emu_io_result_t priv_emu_io_filef(int fd, const char *format,...)
Format bounded text and write it to an explicit raw descriptor.
emu_io_result_t priv_emu_io_err_bytes(const void *bytes, size_t length)
Write an exact non-NUL-terminated byte fragment to the error sink.
void priv_emu_io_txn_abort(emu_io_txn_t *txn)
Close and unlink an active sibling transaction without publication.
emu_io_result_t priv_emu_io_file_char(int fd, char value)
Write one byte to an explicit raw descriptor.
emu_io_result_t priv_emu_io_txn_commit(emu_io_txn_t *txn)
Sync, close, and atomically rename an active sibling transaction.
emu_io_result_t priv_emu_io_file_text(int fd, const char *text)
Write a NUL-terminated literal to an explicit raw descriptor.
emu_io_result_t priv_emu_io_txn_begin(const char *path, emu_io_txn_t *txn)
Create a sibling temporary output for failure-atomic publication.
@ k_prof_max_samples
Chronological stack samples (decimated).
Definition emu_prof.c:86
@ k_prof_max_depth
Deepest call chain captured per sample.
Definition emu_prof.c:85
@ k_prof_samp_every
Default instructions per chain sample.
Definition emu_prof.c:87
static bool internal_prof_json_weights(int fd)
Write the speedscope weights array, parallel to the samples array.
Definition emu_prof.c:508
static uint32_t s_samp_w[k_prof_max_samples]
Per-sample weight (insns).
Definition emu_prof.c:93
static int internal_prof_cmp(const void *a, const void *b)
qsort comparator: order the FUNC symbols by entry address.
Definition emu_prof.c:114
static uint16_t s_pstk[k_prof_max_depth]
Live chain.
Definition emu_prof.c:89
static uint32_t s_prof_n
Definition emu_prof.c:64
prof_mode_t emu_prof_mode(void)
Implementation of emu_prof_mode() – plain state read.
Definition emu_prof.c:949
void emu_prof_set_stop_pc(uint32_t pc)
Implementation of emu_prof_set_stop_pc() – plain state write.
Definition emu_prof.c:961
static uint32_t internal_prof_find(uint32_t pc)
Binary-search the FUNC symbol owning pc; returns s_prof_n if none.
Definition emu_prof.c:190
static const char s_k_prof_html_head[]
Definition emu_prof.c:600
static bool internal_prof_js_frames(int fd)
Write the flamechart page's FRAMES array as JavaScript string literals.
Definition emu_prof.c:539
void prof_add(uint32_t pc, double dt)
Attribute dt wall seconds to pc's function (wall-sample mode).
Definition emu_prof.c:210
static const emu_elf_source_t * s_prof_elf
Borrowed run-long source for names.
Definition emu_prof.c:63
static uint64_t s_samp_acc
Insns since last sample.
Definition emu_prof.c:96
static void internal_prof_write_html(const char *path, uint64_t total)
Write a self-contained, locally-openable HTML flamechart of the samples.
Definition emu_prof.c:665
bool emu_prof_stop_hit(void)
Implementation of emu_prof_stop_hit() – plain flag read.
Definition emu_prof.c:967
static uint32_t s_pstk_n
Chain depth.
Definition emu_prof.c:90
static uint32_t s_samp_n
Stored sample count.
Definition emu_prof.c:94
void emu_prof_install(uc_engine *uc)
Implementation of emu_prof_install() – arms the insn hook in insn mode.
Definition emu_prof.c:940
static uint64_t internal_prof_accumulate_incl_self(void)
Reset then fill s_incl/s_self from the samples; return total weight.
Definition emu_prof.c:718
static void internal_prof_write_speedscope(const char *path)
Perform prof write speedscope for the emu prof model.
Definition emu_prof.c:559
static prof_mode_t s_prof_mode
Definition emu_prof.c:67
static void internal_prof_report_flamechart(void)
Speedscope export + inclusive/self breakdown + phase timeline (insn mode).
Definition emu_prof.c:870
static bool internal_prof_json_frames(int fd)
Write the speedscope shared.frames array: one entry per symbol.
Definition emu_prof.c:443
static const double s_nsec_per_sec
Nanoseconds per second (timespec tv_nsec -> seconds).
Definition emu_prof.c:28
static uint32_t s_prof_stop_pc
RA8_EMU_STOP_PC (0=off).
Definition emu_prof.c:97
static bool internal_prof_name(uint32_t symbol, int fd, char escape, bool error_sink)
Stream one retained-offset symbol name to a file or error sink.
Definition emu_prof.c:416
static void internal_prof_print_phase(uint32_t segfn, uint64_t cum, uint64_t segw, uint64_t total)
Print one boot-timeline row: start percent, width percent, and phase.
Definition emu_prof.c:758
static void internal_prof_sample(uint32_t weight)
Append the live call chain as one chronological sample of weight insns.
Definition emu_prof.c:255
static bool internal_prof_symbol(const emu_elf_symbol_t *symbol, void *ctx)
Collect one sized STT_FUNC symbol into the bounded profiler table.
Definition emu_prof.c:142
void prof_load(const emu_elf_source_t *elf)
Collect + sort FUNC symbols (RA8_EMU_PROFILE only) for PC bucketing.
Definition emu_prof.c:160
static bool internal_prof_name_chunk(const char *bytes, size_t length, void *opaque)
Emit one transient source-name chunk to a selected sink.
Definition emu_prof.c:381
static const char s_k_prof_html_js[]
Definition emu_prof.c:616
static uint64_t s_prof_total_i
Definition emu_prof.c:66
static bool internal_prof_json_samples(int fd)
Write the speedscope samples array: one frame-index stack per sample.
Definition emu_prof.c:473
void prof_report(void)
Print the top hot functions (by wall time or instruction count) at run end.
Definition emu_prof.c:892
prof_phase_t
Boot-timeline "phase" collapse constants for internal_prof_print_boot_timeline.
Definition emu_prof.c:701
@ k_phase_lines
Cap on printed timeline segments.
Definition emu_prof.c:704
@ k_no_fn
Sentinel for "no phase frame".
Definition emu_prof.c:702
@ k_phase_depth
Chain depth used as the boot "phase".
Definition emu_prof.c:703
@ k_phase_pct_x1
Per-cent base: keep segments >= 1%.
Definition emu_prof.c:705
static bool internal_prof_io_ok(emu_io_result_t result)
Convert a host-I/O result to the profiler writer's boolean accumulator.
Definition emu_prof.c:352
static void internal_prof_stack_update(uint32_t f)
Fold f (PC's owning FUNC index) into the live call chain (push/pop).
Definition emu_prof.c:281
static void internal_prof_decimate(void)
Halve the sample store (merge adjacent pairs) when it fills up.
Definition emu_prof.c:230
static const double s_percent_scale
Fraction-to-per-cent scale (fraction * 100.0 == per-cent).
Definition emu_prof.c:698
double board_now_s(void)
Monotonic wall-clock seconds.
Definition emu_prof.c:31
static double s_prof_total_s
Definition emu_prof.c:65
static uint8_t s_samp_d[k_prof_max_samples]
Per-sample chain depth.
Definition emu_prof.c:92
static bool s_prof_stop_hit
Set when STOP_PC reached.
Definition emu_prof.c:98
static void internal_prof_insn_hook(uc_engine *uc, uint64_t address, uint32_t size, void *user)
Perform prof insn hook for the emu prof model.
Definition emu_prof.c:314
static void internal_prof_print_incl_self_table(const char *html, const char *out, uint64_t total)
Print the inclusive/self table (sorted by inclusive weight).
Definition emu_prof.c:831
static uint64_t s_samp_every
Insns per sample (>>x2).
Definition emu_prof.c:95
static prof_sym_t s_prof[k_prof_max_syms]
Definition emu_prof.c:62
static uint64_t s_self[k_prof_max_syms]
Self (leaf) weight.
Definition emu_prof.c:100
static void internal_prof_print_boot_timeline(uint64_t total)
Print the boot timeline: each contiguous shallow-depth phase run.
Definition emu_prof.c:780
@ k_prof_max_syms
Cap on profiled FUNC symbols.
Definition emu_prof.c:51
@ k_prof_top_n
Top entries printed in the report.
Definition emu_prof.c:52
static uint16_t s_samp[k_prof_max_samples][k_prof_max_depth]
root..leaf.
Definition emu_prof.c:91
uint64_t emu_prof_total_insns(void)
Implementation of emu_prof_total_insns() – plain counter read.
Definition emu_prof.c:955
static uint64_t s_incl[k_prof_max_syms]
Inclusive weight (report).
Definition emu_prof.c:99
Firmware profiler (RA8_EMU_PROFILE): sampling, hooks, reports.
prof_mode_t
Profiler mode parsed from RA8_EMU_PROFILE.
Definition emu_prof.h:50
@ k_prof_insn
=full/=insn: exact per-instruction + calls.
Definition emu_prof.h:53
@ k_prof_wall
=1: cheap chunk-start wall-time sampler.
Definition emu_prof.h:52
@ k_prof_off
Disabled (no env, zero cost).
Definition emu_prof.h:51
#define RA8_INTERNAL
Marker that a function is intended to be static (file-local).
int strcmp(const char *s1, const char *s2)
Compare two null-terminated strings.
void * memcpy(void *dst, const void *src, size_t n)
Copy memory area between non-overlapping regions.
One independently owned immutable raw-descriptor ELF source.
Definition emu_elf.h:91
One bounds-checked symbol-table entry plus its string offset.
Definition emu_elf.h:307
uint64_t name_offset
Absolute source offset of the NUL-terminated name.
Definition emu_elf.h:308
uint32_t size
Symbol byte extent.
Definition emu_elf.h:310
uint32_t value
Symbol value.
Definition emu_elf.h:309
uint8_t info
ELF st_info byte.
Definition emu_elf.h:311
Complete, caller-visible result of a raw host-I/O operation.
emu_io_status_t status
Semantic completion status.
Sibling temporary file used for failure-atomic publication.
int fd
Owned temporary descriptor, or -1 when inactive.
Sink configuration for one streamed profiler symbol name.
Definition emu_prof.c:358
int fd
Explicit file descriptor, ignored for error sink.
Definition emu_prof.c:359
bool error_sink
Route bytes to the injected error sink.
Definition emu_prof.c:361
char escape
Character escaped with backslash, or NUL.
Definition emu_prof.c:360
bool ok
Sticky exact-output success.
Definition emu_prof.c:362
uint32_t hi
Function end (lo + st_size).
Definition emu_prof.c:56
uint64_t insns
Instructions executed (insn mode).
Definition emu_prof.c:59
uint64_t calls
Entries to this fn (insn mode).
Definition emu_prof.c:60
uint32_t lo
Function entry (Thumb bit cleared).
Definition emu_prof.c:55
double secs
Wall seconds (wall mode).
Definition emu_prof.c:58
uint64_t name_offset
Source offset of the symbol name.
Definition emu_prof.c:57