thruput_native_exec/
rv32_stack_usage.rs1use core::ffi::c_void;
4use core::ops::Range;
5use thruput_sys::FFIErrorCode;
6use thruput_xdr::types::ContractAddress;
7
8pub const SP_UNWRITTEN: u32 = u32::MAX;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct Rv32StackUsage {
25 pub contract_address: ContractAddress,
27 pub method_id: u64,
29 pub sp_initial: u32,
31 pub sp_min: u32,
33}
34
35impl Rv32StackUsage {
36 pub fn wrote_sp(&self) -> bool {
38 self.sp_initial != SP_UNWRITTEN
39 }
40
41 pub fn bytes_used(&self) -> Option<u32> {
44 self.wrote_sp()
45 .then(|| self.sp_initial.saturating_sub(self.sp_min))
46 }
47
48 fn from_ffi(record: &thruput_sys::FFIRv32StackUsage) -> Self {
49 Self {
50 contract_address: record.contract_address.into(),
51 method_id: record.method_id,
52 sp_initial: record.sp_initial,
53 sp_min: record.sp_min,
54 }
55 }
56}
57
58fn decode_records(bytes: &[u8]) -> Vec<Rv32StackUsage> {
61 let size = size_of::<thruput_sys::FFIRv32StackUsage>();
62 assert!(
63 bytes.len().is_multiple_of(size),
64 "rv32 stack usage buffer of {} bytes is not a whole number of \
65 {size}-byte records",
66 bytes.len()
67 );
68 bytes
69 .chunks_exact(size)
70 .map(|chunk| {
71 let record: thruput_sys::FFIRv32StackUsage = unsafe {
75 core::ptr::read_unaligned(
76 chunk.as_ptr() as *const thruput_sys::FFIRv32StackUsage
77 )
78 };
79 Rv32StackUsage::from_ffi(&record)
80 })
81 .collect()
82}
83
84pub(crate) fn records(handler: *mut c_void) -> Vec<Rv32StackUsage> {
92 let res = unsafe { thruput_sys::get_rv32_stack_usage(handler) };
94 let err: FFIErrorCode = res.ffi_error_code.into();
95 if err != FFIErrorCode::None {
96 panic!("failed to read rv32 stack usage: {err}");
97 }
98 res.records.as_slice().map_or_else(Vec::new, decode_records)
99}
100
101pub(crate) fn frame_records(
107 records: &[Rv32StackUsage],
108 window: Range<usize>,
109 child_windows: &[Range<usize>],
110) -> Vec<Rv32StackUsage> {
111 let start = window.start;
112 records
113 .get(window)
114 .unwrap_or(&[])
115 .iter()
116 .enumerate()
117 .filter(|(offset, _)| {
118 let index = start + offset;
119 !child_windows.iter().any(|child| child.contains(&index))
120 })
121 .map(|(_, record)| *record)
122 .collect()
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 fn ffi_record(
130 address_byte: u8,
131 method_id: u64,
132 sp_initial: u32,
133 sp_min: u32,
134 ) -> thruput_sys::FFIRv32StackUsage {
135 thruput_sys::FFIRv32StackUsage {
136 contract_address: [address_byte; 32],
137 method_id,
138 sp_initial,
139 sp_min,
140 }
141 }
142
143 fn as_bytes(records: &[thruput_sys::FFIRv32StackUsage]) -> &[u8] {
144 unsafe {
145 core::slice::from_raw_parts(
146 records.as_ptr() as *const u8,
147 size_of_val(records),
148 )
149 }
150 }
151
152 #[test]
153 fn decodes_records_in_order_and_computes_usage() {
154 let records = [
155 ffi_record(0xA1, 7, 0x10000, 0x10000 - 96),
156 ffi_record(0xB2, 9, SP_UNWRITTEN, SP_UNWRITTEN),
157 ];
158 let decoded = decode_records(as_bytes(&records));
159 assert_eq!(decoded.len(), 2);
160
161 let first = decoded[0];
162 assert_eq!(first.contract_address, [0xA1u8; 32].into());
163 assert_eq!(first.method_id, 7);
164 assert_eq!(first.sp_initial, 0x10000);
165 assert_eq!(first.sp_min, 0x10000 - 96);
166 assert!(first.wrote_sp());
167 assert_eq!(first.bytes_used(), Some(96));
168
169 let second = decoded[1];
170 assert_eq!(second.method_id, 9);
171 assert!(!second.wrote_sp());
172 assert_eq!(second.bytes_used(), None);
173 }
174
175 #[test]
176 fn empty_buffer_decodes_to_nothing() {
177 assert!(decode_records(&[]).is_empty());
178 }
179
180 #[test]
181 #[should_panic(expected = "not a whole number")]
182 fn partial_record_is_rejected() {
183 let records = [ffi_record(0, 0, 0, 0)];
184 let bytes = as_bytes(&records);
185 let _ = decode_records(&bytes[..bytes.len() - 1]);
186 }
187
188 fn numbered(count: u64) -> Vec<Rv32StackUsage> {
191 (0..count)
192 .map(|n| Rv32StackUsage {
193 contract_address: [n as u8; 32].into(),
194 method_id: n,
195 sp_initial: 0x1000,
196 sp_min: 0x0f00,
197 })
198 .collect()
199 }
200
201 fn method_ids(records: &[Rv32StackUsage]) -> Vec<u64> {
202 records.iter().map(|record| record.method_id).collect()
203 }
204
205 #[test]
206 fn frame_records_are_the_window_less_the_children() {
207 let records = numbered(8);
211 let own = frame_records(&records, 2..7, &[3..5, 6..7]);
212 assert_eq!(method_ids(&own), [2, 5]);
213 assert_eq!(own.last().map(|record| record.method_id), Some(5));
214 }
215
216 #[test]
217 fn frame_records_without_children_is_the_whole_window() {
218 let records = numbered(4);
219 assert_eq!(method_ids(&frame_records(&records, 1..3, &[])), [1, 2]);
220 }
221
222 #[test]
223 fn frame_that_ran_no_rv32_code_has_no_records() {
224 let records = numbered(3);
225 assert!(frame_records(&records, 3..3, &[]).is_empty());
227 assert!(frame_records(&records, 1..3, &[1..3]).is_empty());
229 assert!(frame_records(&[], 0..0, &[]).is_empty());
231 }
232}