pulley_interpreter/
profile.rs1use anyhow::{Context, Result, anyhow, bail};
7use std::fs::{File, OpenOptions};
8use std::io::{BufWriter, Write};
9use std::sync::Arc;
10use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed};
11use std::vec::Vec;
12
13const ID_FUNCTION: u8 = 1;
26
27const ID_SAMPLES: u8 = 2;
35
36#[derive(Default, Clone)]
40pub struct ExecutingPc(Arc<ExecutingPcState>);
41
42#[derive(Default)]
43struct ExecutingPcState {
44    current_pc: AtomicUsize,
45    done: AtomicBool,
46}
47
48impl ExecutingPc {
49    pub(crate) fn as_ref(&self) -> ExecutingPcRef<'_> {
50        ExecutingPcRef(&self.0.current_pc)
51    }
52
53    pub fn get(&self) -> Option<usize> {
56        match self.0.current_pc.load(Relaxed) {
57            0 => None,
58            n => Some(n),
59        }
60    }
61
62    pub fn is_done(&self) -> bool {
65        self.0.done.load(Relaxed)
66    }
67
68    pub(crate) fn set_done(&self) {
69        self.0.done.store(true, Relaxed)
70    }
71}
72
73#[derive(Copy, Clone)]
74#[repr(transparent)]
75pub(crate) struct ExecutingPcRef<'a>(&'a AtomicUsize);
76
77impl ExecutingPcRef<'_> {
78    pub(crate) fn record(&self, pc: usize) {
79        self.0.store(pc, Relaxed);
80    }
81}
82
83pub struct Recorder {
85    file: BufWriter<File>,
89}
90
91impl Recorder {
92    pub fn new(filename: &str) -> Result<Recorder> {
94        Ok(Recorder {
95            file: BufWriter::new(
96                OpenOptions::new()
97                    .write(true)
98                    .create_new(true)
99                    .open(filename)
100                    .with_context(|| format!("failed to open `{filename}` for writing"))?,
101            ),
102        })
103    }
104
105    pub fn add_function(&mut self, name: &str, code: &[u8]) -> Result<()> {
110        self.file.write_all(&[ID_FUNCTION])?;
111        self.file
112            .write_all(&u64::try_from(code.as_ptr() as usize)?.to_le_bytes())?;
113        self.file
114            .write_all(&u32::try_from(name.len())?.to_le_bytes())?;
115        self.file.write_all(name.as_bytes())?;
116        self.file
117            .write_all(&u32::try_from(code.len())?.to_le_bytes())?;
118        self.file.write_all(code)?;
119        Ok(())
120    }
121
122    pub fn add_samples(&mut self, samples: &mut Samples) -> Result<()> {
124        self.file.write_all(&[ID_SAMPLES])?;
125
126        samples.finalize();
127        self.file.write_all(&samples.data)?;
128        samples.reset();
129        Ok(())
130    }
131
132    pub fn flush(&mut self) -> Result<()> {
134        self.file.flush()?;
135        Ok(())
136    }
137}
138
139pub struct Samples {
141    data: Vec<u8>,
142    samples: u32,
143}
144
145impl Samples {
146    pub fn append(&mut self, sample: usize) {
148        self.data.extend_from_slice(&(sample as u64).to_le_bytes());
149        self.samples += 1;
150    }
151
152    pub fn num_samples(&self) -> u32 {
154        self.samples
155    }
156
157    fn finalize(&mut self) {
158        self.data[..4].copy_from_slice(&self.samples.to_le_bytes());
159    }
160
161    fn reset(&mut self) {
162        self.data.truncate(0);
163        self.data.extend_from_slice(&[0; 4]);
164        self.samples = 0;
165    }
166}
167
168impl Default for Samples {
169    fn default() -> Samples {
170        let mut samples = Samples {
171            data: Vec::new(),
172            samples: 0,
173        };
174        samples.reset();
175        samples
176    }
177}
178
179pub enum Event<'a> {
183    Function(u64, &'a str, &'a [u8]),
186    Samples(&'a [SamplePc]),
188}
189
190#[repr(packed)]
192pub struct SamplePc(pub u64);
193
194pub fn decode(mut bytes: &[u8]) -> impl Iterator<Item = Result<Event<'_>>> + use<'_> {
197    std::iter::from_fn(move || {
198        if bytes.is_empty() {
199            None
200        } else {
201            Some(decode_one(&mut bytes))
202        }
203    })
204}
205
206fn decode_one<'a>(bytes: &mut &'a [u8]) -> Result<Event<'a>> {
207    match bytes.split_first().unwrap() {
208        (&ID_FUNCTION, rest) => {
209            let (addr, rest) = rest
210                .split_first_chunk()
211                .ok_or_else(|| anyhow!("invalid addr"))?;
212            let addr = u64::from_le_bytes(*addr);
213
214            let (name_len, rest) = rest
215                .split_first_chunk()
216                .ok_or_else(|| anyhow!("invalid name byte len"))?;
217            let name_len = u32::from_le_bytes(*name_len);
218            let (name, rest) = rest
219                .split_at_checked(name_len as usize)
220                .ok_or_else(|| anyhow!("invalid name contents"))?;
221            let name = std::str::from_utf8(name)?;
222
223            let (body_len, rest) = rest
224                .split_first_chunk()
225                .ok_or_else(|| anyhow!("invalid body byte len"))?;
226            let body_len = u32::from_le_bytes(*body_len);
227            let (body, rest) = rest
228                .split_at_checked(body_len as usize)
229                .ok_or_else(|| anyhow!("invalid body contents"))?;
230
231            *bytes = rest;
232            Ok(Event::Function(addr, name, body))
233        }
234
235        (&ID_SAMPLES, rest) => {
236            let (samples, rest) = rest
237                .split_first_chunk()
238                .ok_or_else(|| anyhow!("invalid sample count"))?;
239            let samples = u32::from_le_bytes(*samples);
240            let (samples, rest) = rest
241                .split_at_checked(samples as usize * 8)
242                .ok_or_else(|| anyhow!("invalid sample data"))?;
243            *bytes = rest;
244
245            let (before, mid, after) = unsafe { samples.align_to::<SamplePc>() };
246            if !before.is_empty() || !after.is_empty() {
247                bail!("invalid sample data contents");
248            }
249            Ok(Event::Samples(mid))
250        }
251
252        _ => bail!("unknown ID in profile"),
253    }
254}