pulley_interpreter/
profile.rs1use anyhow::{anyhow, bail, Context, Result};
7use std::fs::{File, OpenOptions};
8use std::io::{BufWriter, Write};
9use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed};
10use std::sync::Arc;
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}