|
| 1 | +// Copyright 2018-2019 Parity Technologies (UK) Ltd. |
| 2 | +// |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +// you may not use this file except in compliance with the License. |
| 5 | +// You may obtain a copy of the License at |
| 6 | +// |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +// |
| 9 | +// Unless required by applicable law or agreed to in writing, software |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +// See the License for the specific language governing permissions and |
| 13 | +// limitations under the License. |
| 14 | + |
| 15 | +//! This file defines the global buffer arena that is accessible globally |
| 16 | +//! and acts as a cache for allocated heap memory to avoid heap memory |
| 17 | +//! throttling resulting from heap usage for intermediate computations. |
| 18 | +//! |
| 19 | +//! Exactly this happens a lot in the boundaries between SRML contracts |
| 20 | +//! and ink! since encoding and decoding of SCALE values has to be done |
| 21 | +//! in such an intermediate buffer. |
| 22 | +//! |
| 23 | +//! Users and systems are advised to share a common set of allocated buffers |
| 24 | +//! provided by the global buffer arena. |
| 25 | +
|
| 26 | +use crate::{ |
| 27 | + env2::utils::{ |
| 28 | + EnlargeTo, |
| 29 | + Reset, |
| 30 | + }, |
| 31 | + memory::vec::Vec, |
| 32 | +}; |
| 33 | +use cfg_if::cfg_if; |
| 34 | +use core::cell::{ |
| 35 | + Cell, |
| 36 | + RefCell, |
| 37 | +}; |
| 38 | + |
| 39 | +/// The maximum amount of used byte buffers at the same time. |
| 40 | +/// |
| 41 | +/// Since the whole point behind this byte buffer arena is to cache |
| 42 | +/// allocated heap memory the number of concurrent byte buffers that are |
| 43 | +/// in use at the same time should be kept small. |
| 44 | +const IN_USE_LIMIT: usize = 1000; |
| 45 | + |
| 46 | +cfg_if! { |
| 47 | + if #[cfg(feature = "std")] { |
| 48 | + thread_local! { |
| 49 | + /// Global buffer arena that provides shared recycled buffers to avoid |
| 50 | + /// constantly allocating and deallocating heap memory during contract execution. |
| 51 | + /// |
| 52 | + /// # Note |
| 53 | + /// |
| 54 | + /// This is mainly useful to interact with the environment because |
| 55 | + /// it requires intermediate buffers for its encoding and decoding. |
| 56 | + /// |
| 57 | + /// # API |
| 58 | + /// |
| 59 | + /// - Can be accessed through [`std::thread::LocalKey::with`]. |
| 60 | + /// - Provides recycled buffers through the `get_buffer` call. |
| 61 | + pub static BUFFER_ARENA: BufferArena = BufferArena::new(); |
| 62 | + } |
| 63 | + } else { |
| 64 | + /// Global buffer arena that provides shared recycled buffers to avoid |
| 65 | + /// constantly allocating and deallocating heap memory during contract execution. |
| 66 | + /// |
| 67 | + /// # Note |
| 68 | + /// |
| 69 | + /// This is mainly useful to interact with the environment because |
| 70 | + /// it requires intermediate buffers for its encoding and decoding. |
| 71 | + /// |
| 72 | + /// # API |
| 73 | + /// |
| 74 | + /// - Can be accessed through [`std::thread::LocalKey::with`]. |
| 75 | + /// - Provides recycled buffers through the `get_buffer` call. |
| 76 | + pub static BUFFER_ARENA: GlobalBufferArena = GlobalBufferArena::new(BufferArena::new()); |
| 77 | + |
| 78 | + /// Wrapper around `BufferArena` to provide similar interface |
| 79 | + /// as `std::thread::LocalKey` provided by `thread_local` does. |
| 80 | + /// |
| 81 | + /// Also acts as safety guard to prevent references to `BufferRef` |
| 82 | + /// escape the closure using the [`GlobalBufferArena::with`] API. |
| 83 | + pub struct GlobalBufferArena { |
| 84 | + /// The wrapped buffer arena. |
| 85 | + arena: BufferArena, |
| 86 | + } |
| 87 | + |
| 88 | + /// CRITICAL NOTE |
| 89 | + /// ============= |
| 90 | + /// |
| 91 | + /// The wrapped `BufferArena` type itself is __NOT__ `Sync` since it is using |
| 92 | + /// `Cell` and `RefCell` internally instead of the thread-safe alternatives. |
| 93 | + /// However, since Wasm smart contracts are guaranteed to operate single |
| 94 | + /// threaded we can allow for this unsafe `Sync` implementation to allow |
| 95 | + /// for having the global static `BUFFER_ARENA` variable and as long as we |
| 96 | + /// are only operating single threaded this shouldn't be unsafe. |
| 97 | + unsafe impl Sync for GlobalBufferArena {} |
| 98 | + |
| 99 | + impl GlobalBufferArena { |
| 100 | + /// Creates a new `GlobalBufferArena` from the given `BufferArena`. |
| 101 | + pub const fn new(arena: BufferArena) -> Self { |
| 102 | + Self { arena } |
| 103 | + } |
| 104 | + |
| 105 | + /// Runs the given closure for the wrapped `BufferArena`. |
| 106 | + /// |
| 107 | + /// This way no references may escape the closure. |
| 108 | + pub fn with<F>(&self, f: F) |
| 109 | + where |
| 110 | + F: FnOnce(&BufferArena), |
| 111 | + { |
| 112 | + f(&self.arena) |
| 113 | + } |
| 114 | + } |
| 115 | + } |
| 116 | +} |
| 117 | + |
| 118 | +/// A byte buffer arena that acts as a cache for allocated heap memory. |
| 119 | +pub struct BufferArena { |
| 120 | + /// The currently available byte buffers. |
| 121 | + free: RefCell<Vec<Buffer>>, |
| 122 | + /// Counts the buffers that are currently in use at the same time. |
| 123 | + /// |
| 124 | + /// # Note |
| 125 | + /// |
| 126 | + /// This value is purely used as diagnostic measures to provide |
| 127 | + /// smart contract writers with feedback if their implementation |
| 128 | + /// is abusing the buffer arena. |
| 129 | + /// We might want to turn these checks off for Wasm compilation. |
| 130 | + in_use: Cell<usize>, |
| 131 | +} |
| 132 | + |
| 133 | +impl BufferArena { |
| 134 | + /// Returns a new empty buffer arena. |
| 135 | + /// |
| 136 | + /// Since this acts as cache we only require one instance of this type |
| 137 | + /// that we use as `thread_local` global which is safe since |
| 138 | + /// Wasm smart contracts are guaranteed to run in a single thread. |
| 139 | + pub(self) const fn new() -> Self { |
| 140 | + Self { |
| 141 | + free: RefCell::new(Vec::new()), |
| 142 | + in_use: Cell::new(0), |
| 143 | + } |
| 144 | + } |
| 145 | + |
| 146 | + /// Returns a fresh buffer that can be used for intermediate computations. |
| 147 | + /// |
| 148 | + /// Buffers returned through this API implement all the necessary traits |
| 149 | + /// required to use them as environment buffer. |
| 150 | + /// |
| 151 | + /// - [`Reset`]: Allows resetting the buffer. This clears all the elements, |
| 152 | + /// however, it retains the memory allocation. |
| 153 | + /// - [`EnlargeTo`]: Safely enlarges the buffer to the required minimum size |
| 154 | + /// if it isn't already large enough. |
| 155 | + /// - [`core::convert::AsRef`]`<[u8]>`: Returns a shared view into the byte buffer. |
| 156 | + /// - [`core::convert::AsMut`]`<[u8]>`: Returns an exclusive view into the byte buffer. |
| 157 | + pub fn get_buffer(&self) -> BufferRef { |
| 158 | + self.in_use.set(self.in_use() + 1); |
| 159 | + if self.in_use() > IN_USE_LIMIT { |
| 160 | + panic!("too many concurrent byte buffers") |
| 161 | + } |
| 162 | + self.free |
| 163 | + .borrow_mut() |
| 164 | + .pop() |
| 165 | + .unwrap_or_else(Buffer::new) |
| 166 | + .into_ref() |
| 167 | + } |
| 168 | + |
| 169 | + /// Returns the buffer to the arena. |
| 170 | + /// |
| 171 | + /// This is only called from the `Drop` implementation of `BufferRef` |
| 172 | + /// to return the wrapped buffer back to the global buffer arena instance. |
| 173 | + pub(self) fn return_buffer(&self, buffer: Buffer) { |
| 174 | + self.in_use.set(self.in_use() - 1); |
| 175 | + self.free.borrow_mut().push(buffer) |
| 176 | + } |
| 177 | + |
| 178 | + /// Returns the number of buffers that are currently in use at the same time. |
| 179 | + pub fn in_use(&self) -> usize { |
| 180 | + self.in_use.get() |
| 181 | + } |
| 182 | + |
| 183 | + /// Returns the number of buffers that are not in use. |
| 184 | + pub fn free(&self) -> usize { |
| 185 | + self.free.borrow().len() |
| 186 | + } |
| 187 | + |
| 188 | + /// Returns the current number of cached buffers. |
| 189 | + pub fn allocated(&self) -> usize { |
| 190 | + self.in_use() + self.free() |
| 191 | + } |
| 192 | +} |
| 193 | + |
| 194 | +/// A byte buffer. |
| 195 | +/// |
| 196 | +/// This is a thin wrapper around a byte vector providing only |
| 197 | +/// the minimal interface to be operable as environmental intermediate |
| 198 | +/// buffer. |
| 199 | +pub struct Buffer { |
| 200 | + /// The warpper internal raw byte buffer. |
| 201 | + buffer: Vec<u8>, |
| 202 | +} |
| 203 | + |
| 204 | +impl<'a> Buffer |
| 205 | +where |
| 206 | + Self: 'a, |
| 207 | +{ |
| 208 | + /// Returns a new empty byte buffer. |
| 209 | + pub(self) const fn new() -> Self { |
| 210 | + Self { buffer: Vec::new() } |
| 211 | + } |
| 212 | + |
| 213 | + /// Wraps `self` in a buffer reference. |
| 214 | + pub(self) fn into_ref(self) -> BufferRef<'a> { |
| 215 | + BufferRef { |
| 216 | + buffer: self, |
| 217 | + lt: core::marker::PhantomData, |
| 218 | + } |
| 219 | + } |
| 220 | +} |
| 221 | + |
| 222 | +impl Reset for Buffer { |
| 223 | + fn reset(&mut self) { |
| 224 | + Reset::reset(&mut self.buffer) |
| 225 | + } |
| 226 | +} |
| 227 | + |
| 228 | +impl EnlargeTo for Buffer { |
| 229 | + fn enlarge_to(&mut self, new_size: usize) { |
| 230 | + EnlargeTo::enlarge_to(&mut self.buffer, new_size) |
| 231 | + } |
| 232 | +} |
| 233 | + |
| 234 | +impl core::convert::AsRef<[u8]> for Buffer { |
| 235 | + fn as_ref(&self) -> &[u8] { |
| 236 | + &self.buffer |
| 237 | + } |
| 238 | +} |
| 239 | + |
| 240 | +impl core::convert::AsMut<[u8]> for Buffer { |
| 241 | + fn as_mut(&mut self) -> &mut [u8] { |
| 242 | + &mut self.buffer |
| 243 | + } |
| 244 | +} |
| 245 | + |
| 246 | +/// A buffer reference providing lifetime constrained access |
| 247 | +/// to the wrapper byte buffer. |
| 248 | +/// |
| 249 | +/// Buffer references are created by the only global buffer arena |
| 250 | +/// instance and make sure that their wrapper buffer is returned to |
| 251 | +/// the arena upon `Drop`. |
| 252 | +pub struct BufferRef<'a> { |
| 253 | + /// The wrapped byte buffer. |
| 254 | + buffer: Buffer, |
| 255 | + /// The emulated lifetime. |
| 256 | + lt: core::marker::PhantomData<fn() -> &'a ()>, |
| 257 | +} |
| 258 | + |
| 259 | +impl BufferRef<'_> { |
| 260 | + /// Takes the buffer out of the buffer reference |
| 261 | + /// leaving behind an empty byte buffer without |
| 262 | + /// associated heap allocation. |
| 263 | + /// |
| 264 | + /// Also resets the byte buffer. |
| 265 | + fn take_buffer(&mut self) -> Buffer { |
| 266 | + Reset::reset(&mut self.buffer); |
| 267 | + core::mem::replace(&mut self.buffer, Buffer::new()) |
| 268 | + } |
| 269 | +} |
| 270 | + |
| 271 | +impl Reset for BufferRef<'_> { |
| 272 | + fn reset(&mut self) { |
| 273 | + Reset::reset(&mut self.buffer) |
| 274 | + } |
| 275 | +} |
| 276 | + |
| 277 | +impl EnlargeTo for BufferRef<'_> { |
| 278 | + fn enlarge_to(&mut self, new_size: usize) { |
| 279 | + EnlargeTo::enlarge_to(&mut self.buffer, new_size) |
| 280 | + } |
| 281 | +} |
| 282 | + |
| 283 | +impl core::convert::AsRef<[u8]> for BufferRef<'_> { |
| 284 | + fn as_ref(&self) -> &[u8] { |
| 285 | + core::convert::AsRef::<[u8]>::as_ref(&self.buffer) |
| 286 | + } |
| 287 | +} |
| 288 | + |
| 289 | +impl core::convert::AsMut<[u8]> for BufferRef<'_> { |
| 290 | + fn as_mut(&mut self) -> &mut [u8] { |
| 291 | + core::convert::AsMut::<[u8]>::as_mut(&mut self.buffer) |
| 292 | + } |
| 293 | +} |
| 294 | + |
| 295 | +impl Drop for BufferRef<'_> { |
| 296 | + fn drop(&mut self) { |
| 297 | + // Returns the byte buffer back to the global buffer arena. |
| 298 | + BUFFER_ARENA.with(|arena| arena.return_buffer(self.take_buffer())) |
| 299 | + } |
| 300 | +} |
| 301 | + |
| 302 | +#[cfg(test)] |
| 303 | +mod tests { |
| 304 | + use super::*; |
| 305 | + |
| 306 | + macro_rules! assert_arena { |
| 307 | + ( |
| 308 | + $arena:ident, |
| 309 | + in_use: $expected_in_use:literal, |
| 310 | + free: $expected_free:literal, |
| 311 | + allocated: $expected_allocated:literal |
| 312 | + ) => {{ |
| 313 | + assert_eq!( |
| 314 | + $arena.in_use(), |
| 315 | + $expected_in_use, |
| 316 | + "number of buffers in use doesn't match expected" |
| 317 | + ); |
| 318 | + assert_eq!( |
| 319 | + $arena.free(), |
| 320 | + $expected_free, |
| 321 | + "number of free buffers doens't match expected" |
| 322 | + ); |
| 323 | + assert_eq!( |
| 324 | + $arena.allocated(), |
| 325 | + $expected_allocated, |
| 326 | + "number of allocated buffers doesn't match expected" |
| 327 | + ); |
| 328 | + }}; |
| 329 | + } |
| 330 | + |
| 331 | + #[test] |
| 332 | + fn it_works() { |
| 333 | + BUFFER_ARENA.with(|arena| { |
| 334 | + assert_arena!(arena, in_use: 0, free: 0, allocated: 0); |
| 335 | + // Allocate a single buffer for a short time. |
| 336 | + { |
| 337 | + let _b = arena.get_buffer(); |
| 338 | + assert_arena!(arena, in_use: 1, free: 0, allocated: 1); |
| 339 | + } |
| 340 | + // We should now have a single allocated buffer |
| 341 | + // but none in use. |
| 342 | + assert_arena!(arena, in_use: 0, free: 1, allocated: 1); |
| 343 | + // Allocate a single buffer again so that we see |
| 344 | + // it is being reused. |
| 345 | + { |
| 346 | + let _b = arena.get_buffer(); |
| 347 | + assert_arena!(arena, in_use: 1, free: 0, allocated: 1); |
| 348 | + } |
| 349 | + assert_arena!(arena, in_use: 0, free: 1, allocated: 1); |
| 350 | + // Now we allocate 3 buffers in their own scope |
| 351 | + // and check the `in_use` and `allocated`. |
| 352 | + { |
| 353 | + let _b0 = arena.get_buffer(); |
| 354 | + { |
| 355 | + let _b1 = arena.get_buffer(); |
| 356 | + { |
| 357 | + // At this point we should have 3 buffers |
| 358 | + // allocated and in use. |
| 359 | + let _b2 = arena.get_buffer(); |
| 360 | + assert_arena!(arena, in_use: 3, free: 0, allocated: 3); |
| 361 | + } |
| 362 | + assert_arena!(arena, in_use: 2, free: 1, allocated: 3); |
| 363 | + } |
| 364 | + assert_arena!(arena, in_use: 1, free: 2, allocated: 3); |
| 365 | + } |
| 366 | + // At this point we dropped all 3 buffers again |
| 367 | + // so none is in use but we still have 3 allocated |
| 368 | + // buffers. |
| 369 | + assert_arena!(arena, in_use: 0, free: 3, allocated: 3); |
| 370 | + }); |
| 371 | + } |
| 372 | +} |
0 commit comments