From 1e1a5df862d8f0739a2c72197fa1620987945f3a Mon Sep 17 00:00:00 2001
From: Johannes Stoelp <johannes.stoelp@gmail.com>
Date: Wed, 28 Feb 2024 18:55:10 +0100
Subject: rt: add perf static jit profile support

---
 src/rt.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 60 insertions(+), 2 deletions(-)

(limited to 'src')

diff --git a/src/rt.rs b/src/rt.rs
index a27a67f..f468033 100644
--- a/src/rt.rs
+++ b/src/rt.rs
@@ -3,16 +3,62 @@
 //! This runtime supports adding code to executable pages and turn the added code into user
 //! specified function pointer.
 
-use nix::sys::mman::{mmap, mprotect, munmap, MapFlags, ProtFlags};
-
 #[cfg(not(target_os = "linux"))]
 compile_error!("This runtime is only supported on linux");
 
+use nix::sys::mman::{mmap, mprotect, munmap, MapFlags, ProtFlags};
+
+mod perf {
+    use std::fs;
+    use std::io::Write;
+
+    /// Provide support for the simple [perf jit interface][perf-jit].
+    ///
+    /// This allows a simple (static) jit runtime to generate meta data describing the generated
+    /// functions, which is used during post-processing by `perf report` to symbolize addresses
+    /// captured while executing jitted code.
+    ///
+    /// By the nature of this format, this can not be used for dynamic jit runtimes, which reuses
+    /// memory which previously contained jitted code.
+    ///
+    /// [perf-jit]: https://elixir.bootlin.com/linux/v6.6.6/source/tools/perf/Documentation/jit-interface.txt
+    pub(super) struct PerfMap {
+        file: std::fs::File,
+    }
+
+    impl PerfMap {
+        /// Create an empty perf map file.
+        pub(super) fn new() -> Self {
+            let name = format!("/tmp/perf-{}.map", nix::unistd::getpid());
+            let file = fs::OpenOptions::new()
+                .truncate(true)
+                .create(true)
+                .write(true)
+                .open(&name)
+                .unwrap_or_else(|_| panic!("Failed to open perf map file {}", &name));
+
+            PerfMap { file }
+        }
+
+        /// Add an entry to the perf map file.
+        pub(super) fn add_entry(&mut self, start: usize, len: usize) {
+            // Each line has the following format, fields separated with spaces:
+            //   START SIZE NAME
+            //
+            // START and SIZE are hex numbers without 0x.
+            // NAME is the rest of the line, so it could contain special characters.
+            writeln!(self.file, "{:x} {:x} jitfn_{:x}", start, len, start)
+                .expect("Failed to write PerfMap entry");
+        }
+    }
+}
+
 /// A simple `mmap`ed runtime with executable pages.
 pub struct Runtime {
     buf: *mut u8,
     len: usize,
     idx: usize,
+    perf: Option<perf::PerfMap>,
 }
 
 impl Runtime {
@@ -40,9 +86,16 @@ impl Runtime {
             buf,
             len: len.get(),
             idx: 0,
+            perf: None,
         }
     }
 
+    pub fn with_profile() -> Runtime {
+        let mut rt = Runtime::new();
+        rt.perf = Some(perf::PerfMap::new());
+        rt
+    }
+
     /// Add the block of `code` to the runtime and a get function pointer of type `F`.
     ///
     /// # Panics
@@ -83,6 +136,11 @@ impl Runtime {
         // Increment index to next free byte.
         self.idx += code.len();
 
+        // Add perf map entry.
+        if let Some(map) = &mut self.perf {
+            map.add_entry(fn_start as usize, code.len());
+        }
+
         // Return function to newly added code.
         unsafe { Self::as_fn::<F>(fn_start) }
     }
-- 
cgit v1.2.3