diff --git a/agb/examples/sprites.rs b/agb/examples/sprites.rs index badda4a8..486f53c0 100644 --- a/agb/examples/sprites.rs +++ b/agb/examples/sprites.rs @@ -5,6 +5,7 @@ extern crate alloc; use agb::display::object::{Graphics, ObjectController, Sprite, TagMap}; use alloc::vec::Vec; +use bare_metal::CriticalSection; const GRAPHICS: &Graphics = agb::include_aseprite!( "../examples/the-purple-night/gfx/objects.aseprite", @@ -100,6 +101,10 @@ fn all_tags(gfx: &ObjectController) { fn main(mut gba: agb::Gba) -> ! { let gfx = gba.display.object.get(); + let mut timers = gba.timers.timers(); + + let _a = agb::interrupt::profiler(&mut timers.timer0, 5000); + loop { all_tags(&gfx); all_sprites(&gfx); diff --git a/agb/interrupt_handler.s b/agb/interrupt_handler.s index a68814e8..8803ca4d 100644 --- a/agb/interrupt_handler.s +++ b/agb/interrupt_handler.s @@ -1,3 +1,4 @@ + @ An interrupt handler that simply acknowledges all interrupts .arm .global InterruptHandler @@ -14,6 +15,10 @@ InterruptHandler: ldrh r3, [r2, #2] @ load 16 bit interrupt request to r3 and r0, r1, r3 @ interrupts both enabled and requested + ldr r1, [sp, #20] + ldr r3, =agb_rs__program_counter + str r1, [r3] + @ change to system mode mrs r1, cpsr orr r1, r1, #0xD @@ -43,3 +48,10 @@ InterruptHandler: bx lr @ return to bios .pool + + +.section .iwram + .global agb_rs__program_counter + .balign 4 +agb_rs__program_counter: + .word 0 diff --git a/agb/src/interrupt.rs b/agb/src/interrupt.rs index 701e6d89..111e777f 100644 --- a/agb/src/interrupt.rs +++ b/agb/src/interrupt.rs @@ -360,3 +360,22 @@ mod tests { ); } } + +#[must_use] +/// The behaviour of this function is undefined in the sense that it will output +/// some information in some way that can be interpreted in a way to give some +/// profiling information. What it outputs, how it outputs it, and how to +/// interpret it are all subject to change at any time. +/// +/// With that out of the way, the current version will, in mgba, output the +/// program counter at regular intervals. This can be used to see hot functions +/// using, for example, addr2line. +pub fn profiler(timer: &mut crate::timer::Timer, period: u16) -> InterruptHandler { + timer.set_interrupt(true); + timer.set_overflow_amount(period); + timer.set_enabled(true); + + add_interrupt_handler(timer.get_interrupt(), |_key: &CriticalSection| { + crate::println!("{:#010x}", crate::get_program_counter_before_interrupt()); + }) +} diff --git a/agb/src/lib.rs b/agb/src/lib.rs index 9d946b69..a4290c96 100644 --- a/agb/src/lib.rs +++ b/agb/src/lib.rs @@ -407,3 +407,11 @@ mod test { } } } + +#[inline(never)] +pub fn get_program_counter_before_interrupt() -> u32 { + extern "C" { + static mut agb_rs__program_counter: u32; + } + unsafe { agb_rs__program_counter } +} diff --git a/agb/src/timer.rs b/agb/src/timer.rs index f5ebcb7f..75befee8 100644 --- a/agb/src/timer.rs +++ b/agb/src/timer.rs @@ -90,6 +90,11 @@ impl Timer { self.control_register().set_bits(bit, 1, 2); } + pub fn set_interrupt(&mut self, interrupt: bool) { + let bit = interrupt as u16; + self.control_register().set_bits(bit, 1, 6); + } + fn data_register(&self) -> MemoryMapped { timer_data(self.get_timer_number()) } @@ -101,6 +106,17 @@ impl Timer { fn get_timer_number(&self) -> usize { self.timer_number as usize } + + pub fn get_interrupt(&self) -> crate::interrupt::Interrupt { + use crate::interrupt::Interrupt; + match self.timer_number { + 0 => Interrupt::Timer0, + 1 => Interrupt::Timer1, + 2 => Interrupt::Timer2, + 3 => Interrupt::Timer3, + _ => unreachable!(), + } + } } #[non_exhaustive]