1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
//! Local context trait [`LocalContextTr`] and related types.
use core::{
cell::{Ref, RefCell},
ops::Range,
};
use std::{rc::Rc, string::String, vec::Vec};
/// Non-empty, item-pooling Vec.
#[derive(Debug, Clone)]
pub struct FrameStack<T> {
stack: Vec<T>,
index: Option<usize>,
}
impl<T> Default for FrameStack<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> FrameStack<T> {
/// Creates a new, empty stack. It must be initialized with init before use.
pub fn new() -> Self {
// p99.9 of call frame depth is 8,
// per: https://ethresear.ch/t/evm-stack-and-memory-usage-statistics-report/24209
Self {
stack: Vec::with_capacity(8),
index: None,
}
}
/// Creates a new stack with preallocated items by calling `T::default()` `len` times.
/// Index will still be `None` until `end_init` is called.
pub fn new_prealloc(len: usize) -> Self
where
T: Default,
{
let mut stack = Vec::with_capacity(len);
stack.resize_with(len, T::default);
Self { stack, index: None }
}
/// Initializes the stack with a single item.
#[inline]
pub fn start_init(&mut self) -> OutFrame<'_, T> {
self.index = None;
if self.stack.is_empty() {
self.stack.reserve(8);
}
self.out_frame_at(0)
}
/// Finishes initialization.
///
/// # Safety
///
/// This method is unsafe because it assumes that the `token` is initialized from this FrameStack object.
#[inline]
pub unsafe fn end_init(&mut self, token: FrameToken) {
token.assert();
if self.stack.is_empty() {
unsafe { self.stack.set_len(1) };
}
self.index = Some(0);
}
/// Returns the current index of the stack.
#[inline]
pub fn index(&self) -> Option<usize> {
self.index
}
/// Increments the index.
///
/// # Safety
///
/// This method is unsafe because it assumes that the `token` is obtained from `get_next` and
/// that `end_init` is called to initialize the FrameStack.
#[inline]
pub unsafe fn push(&mut self, token: FrameToken) {
token.assert();
let index = self.index.as_mut().unwrap();
*index += 1;
// capacity of stack is incremented in `get_next`
debug_assert!(
*index < self.stack.capacity(),
"Stack capacity is not enough for index"
);
// If the index is the last one, we need to increase the length.
if *index == self.stack.len() {
unsafe { self.stack.set_len(self.stack.len() + 1) };
}
}
/// Clears the stack by setting the index to 0.
/// It does not destroy the stack.
#[inline]
pub fn clear(&mut self) {
self.index = None;
}
/// Decrements the index.
#[inline]
pub fn pop(&mut self) {
self.index = self.index.unwrap_or(0).checked_sub(1);
}
/// Returns the current item.
#[inline]
pub fn get(&mut self) -> &mut T {
debug_assert!(
self.stack.capacity() > self.index.unwrap(),
"Stack capacity is not enough for index"
);
unsafe { &mut *self.stack.as_mut_ptr().add(self.index.unwrap()) }
}
/// Get next uninitialized item.
#[inline]
pub fn get_next(&mut self) -> OutFrame<'_, T> {
if self.index.unwrap() + 1 == self.stack.capacity() {
// allocate 8 more items
self.stack.reserve(8);
}
self.out_frame_at(self.index.unwrap() + 1)
}
fn out_frame_at(&mut self, idx: usize) -> OutFrame<'_, T> {
unsafe {
OutFrame::new_maybe_uninit(self.stack.as_mut_ptr().add(idx), idx < self.stack.len())
}
}
}
/// A potentially initialized frame. Used when initializing a new frame in the main loop.
#[allow(missing_debug_implementations)]
pub struct OutFrame<'a, T> {
ptr: *mut T,
init: bool,
lt: core::marker::PhantomData<&'a mut T>,
}
impl<'a, T> OutFrame<'a, T> {
/// Creates a new initialized `OutFrame` from a mutable reference to a type `T`.
pub fn new_init(slot: &'a mut T) -> Self {
unsafe { Self::new_maybe_uninit(slot, true) }
}
/// Creates a new uninitialized `OutFrame` from a mutable reference to a `MaybeUninit<T>`.
pub fn new_uninit(slot: &'a mut core::mem::MaybeUninit<T>) -> Self {
unsafe { Self::new_maybe_uninit(slot.as_mut_ptr(), false) }
}
/// Creates a new `OutFrame` from a raw pointer to a type `T`.
///
/// # Safety
///
/// This method is unsafe because it assumes that the pointer is valid and points to a location
/// where a type `T` can be stored. It also assumes that the `init` flag correctly reflects whether
/// the type `T` has been initialized or not.
pub unsafe fn new_maybe_uninit(ptr: *mut T, init: bool) -> Self {
Self {
ptr,
init,
lt: Default::default(),
}
}
/// Returns a mutable reference to the type `T`, initializing it if it hasn't been initialized yet.
pub fn get(&mut self, f: impl FnOnce() -> T) -> &mut T {
if !self.init {
self.do_init(f);
}
unsafe { &mut *self.ptr }
}
#[inline(never)]
#[cold]
fn do_init(&mut self, f: impl FnOnce() -> T) {
unsafe {
self.init = true;
self.ptr.write(f());
}
}
/// Returns a mutable reference to the type `T`, without checking if it has been initialized.
///
/// # Safety
///
/// This method is unsafe because it assumes that the `OutFrame` has been initialized before use.
pub unsafe fn get_unchecked(&mut self) -> &mut T {
debug_assert!(self.init, "OutFrame must be initialized before use");
unsafe { &mut *self.ptr }
}
/// Consumes the `OutFrame`, returning a `FrameToken` that indicates the frame has been initialized.
pub fn consume(self) -> FrameToken {
FrameToken(self.init)
}
}
/// Used to guarantee that a frame is initialized before use.
#[allow(missing_debug_implementations)]
pub struct FrameToken(bool);
impl FrameToken {
/// Asserts that the frame token is initialized.
#[cfg_attr(debug_assertions, track_caller)]
pub fn assert(self) {
assert!(self.0, "FrameToken must be initialized before use");
}
}
/// Local context used for caching initcode from Initcode transactions.
pub trait LocalContextTr {
/// Interpreter shared memory buffer. A reused memory buffer for calls.
fn shared_memory_buffer(&self) -> &Rc<RefCell<Vec<u8>>>;
/// Slice of the shared memory buffer returns None if range is not valid or buffer can't be borrowed.
fn shared_memory_buffer_slice(&self, range: Range<usize>) -> Option<Ref<'_, [u8]>> {
Ref::filter_map(self.shared_memory_buffer().borrow(), |b| b.get(range)).ok()
}
/// Clear the local context.
fn clear(&mut self);
/// Set the error message for a precompile error, if any.
///
/// This is used to bubble up precompile error messages when the
/// transaction directly targets a precompile (depth == 1).
fn set_precompile_error_context(&mut self, _output: String);
/// Take and clear the precompile error context, if present.
///
/// Returns `Some(String)` if a precompile error message was recorded.
fn take_precompile_error_context(&mut self) -> Option<String>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn frame_stack() {
let mut stack = FrameStack::new_prealloc(1);
let mut frame = stack.start_init();
// it is already initialized to zero.
*frame.get(|| 2) += 1;
let token = frame.consume();
unsafe { stack.end_init(token) };
assert_eq!(stack.index(), Some(0));
assert_eq!(stack.stack.len(), 1);
let a = stack.get();
assert_eq!(a, &mut 1);
let mut b = stack.get_next();
assert!(!b.init);
assert_eq!(b.get(|| 2), &mut 2);
let token = b.consume(); // TODO: remove
unsafe { stack.push(token) };
assert_eq!(stack.index(), Some(1));
assert_eq!(stack.stack.len(), 2);
let a = stack.get();
assert_eq!(a, &mut 2);
let b = stack.get_next();
assert!(!b.init);
stack.pop();
assert_eq!(stack.index(), Some(0));
assert_eq!(stack.stack.len(), 2);
let a = stack.get();
assert_eq!(a, &mut 1);
let mut b = stack.get_next();
assert!(b.init);
assert_eq!(unsafe { b.get_unchecked() }, &mut 2);
}
}