1use std::{cell::RefCell, rc::Rc};
7
8use crate::WASM_PAGE_SIZE_BYTES;
9
10pub trait Memory {
17 fn size(&self) -> u64;
19
20 fn grow(&self, pages: u64) -> i64;
23
24 fn read(&self, offset: u64, bytes: &mut [u8]);
30
31 #[expect(
49 unsafe_code,
50 reason = "The raw destination is an explicit caller-owned boundary."
51 )]
52 unsafe fn read_unsafe(&self, offset: u64, dst: *mut u8, count: usize) {
53 admit_range(offset, count, self.size());
54 let mut scratch = [0_u8; 1024];
55 let mut copied = 0;
56 while copied < count {
57 let chunk = (count - copied).min(scratch.len());
58 let source_offset = offset + u64::try_from(copied).expect("admitted offset");
59 self.read(source_offset, &mut scratch[..chunk]);
60 unsafe {
64 std::ptr::copy_nonoverlapping(scratch.as_ptr(), dst.add(copied), chunk);
65 }
66 copied += chunk;
67 }
68 }
69
70 fn write(&self, offset: u64, bytes: &[u8]);
76}
77
78impl<M: Memory + ?Sized> Memory for Rc<M> {
79 fn size(&self) -> u64 {
80 (**self).size()
81 }
82
83 fn grow(&self, pages: u64) -> i64 {
84 (**self).grow(pages)
85 }
86
87 fn read(&self, offset: u64, bytes: &mut [u8]) {
88 (**self).read(offset, bytes);
89 }
90
91 #[expect(
92 unsafe_code,
93 reason = "Forwarding preserves the caller's raw-read obligations."
94 )]
95 unsafe fn read_unsafe(&self, offset: u64, dst: *mut u8, count: usize) {
96 unsafe { (**self).read_unsafe(offset, dst, count) };
98 }
99
100 fn write(&self, offset: u64, bytes: &[u8]) {
101 (**self).write(offset, bytes);
102 }
103}
104
105impl<M: Memory + ?Sized> Memory for &M {
106 fn size(&self) -> u64 {
107 (**self).size()
108 }
109
110 fn grow(&self, pages: u64) -> i64 {
111 (**self).grow(pages)
112 }
113
114 fn read(&self, offset: u64, bytes: &mut [u8]) {
115 (**self).read(offset, bytes);
116 }
117
118 #[expect(
119 unsafe_code,
120 reason = "Forwarding preserves the caller's raw-read obligations."
121 )]
122 unsafe fn read_unsafe(&self, offset: u64, dst: *mut u8, count: usize) {
123 unsafe { (**self).read_unsafe(offset, dst, count) };
125 }
126
127 fn write(&self, offset: u64, bytes: &[u8]) {
128 (**self).write(offset, bytes);
129 }
130}
131
132pub type VectorMemory = Rc<RefCell<Vec<u8>>>;
138
139#[cfg(not(target_arch = "wasm32"))]
145pub type DefaultMemoryImpl = VectorMemory;
146
147impl Memory for RefCell<Vec<u8>> {
148 fn size(&self) -> u64 {
149 u64::try_from(self.borrow().len()).expect("native byte length fits u64")
150 / WASM_PAGE_SIZE_BYTES
151 }
152
153 fn grow(&self, pages: u64) -> i64 {
154 let mut bytes = self.borrow_mut();
155 let length = u64::try_from(bytes.len()).expect("native byte length fits u64");
156 let old_pages = length / WASM_PAGE_SIZE_BYTES;
157 let Ok(previous) = i64::try_from(old_pages) else {
158 return -1;
159 };
160 if pages == 0 {
161 return previous;
162 }
163 if !length.is_multiple_of(WASM_PAGE_SIZE_BYTES) {
166 return -1;
167 }
168 let Some(new_length) = old_pages
169 .checked_add(pages)
170 .and_then(|total| total.checked_mul(WASM_PAGE_SIZE_BYTES))
171 .and_then(|total| usize::try_from(total).ok())
172 else {
173 return -1;
174 };
175 let additional = new_length - bytes.len();
176 if bytes.try_reserve_exact(additional).is_err() {
177 return -1;
178 }
179 bytes.resize(new_length, 0);
180 previous
181 }
182
183 fn read(&self, offset: u64, bytes: &mut [u8]) {
184 let backing = self.borrow();
185 let range = native_range(offset, bytes.len(), backing.len());
186 bytes.copy_from_slice(&backing[range]);
187 }
188
189 #[expect(
190 unsafe_code,
191 reason = "Direct copying initializes caller-owned raw output without a temporary."
192 )]
193 unsafe fn read_unsafe(&self, offset: u64, dst: *mut u8, count: usize) {
194 let backing = self.borrow();
195 let range = native_range(offset, count, backing.len());
196 unsafe {
199 std::ptr::copy_nonoverlapping(backing[range].as_ptr(), dst, count);
200 }
201 }
202
203 fn write(&self, offset: u64, bytes: &[u8]) {
204 let mut backing = self.borrow_mut();
205 let range = native_range(offset, bytes.len(), backing.len());
206 backing[range].copy_from_slice(bytes);
207 }
208}
209
210fn admit_range(offset: u64, count: usize, pages: u64) {
213 let extent = pages
214 .checked_mul(WASM_PAGE_SIZE_BYTES)
215 .expect("memory extent overflow");
216 let count = u64::try_from(count).expect("memory access length overflow");
217 let end = offset.checked_add(count).expect("memory access overflow");
218 assert!(end <= extent, "memory access outside complete pages");
219}
220
221fn native_range(offset: u64, count: usize, length: usize) -> std::ops::Range<usize> {
222 let pages = u64::try_from(length).expect("native byte length fits u64") / WASM_PAGE_SIZE_BYTES;
223 admit_range(offset, count, pages);
224 let start = usize::try_from(offset).expect("admitted native offset");
225 start..start + count
226}
227
228#[cfg(target_arch = "wasm32")]
234#[derive(Clone, Copy, Default)]
235pub struct DefaultMemoryImpl {
236 _private: (),
237}
238
239#[cfg(target_arch = "wasm32")]
240impl Memory for DefaultMemoryImpl {
241 fn size(&self) -> u64 {
242 ic_platform_runtime::ic0::stable64_size()
243 }
244
245 fn grow(&self, pages: u64) -> i64 {
246 let old_pages = self.size();
247 let Ok(previous) = i64::try_from(old_pages) else {
248 return -1;
249 };
250 if pages == 0 {
251 return previous;
252 }
253 if old_pages
254 .checked_add(pages)
255 .and_then(|total| total.checked_mul(WASM_PAGE_SIZE_BYTES))
256 .is_none()
257 {
258 return -1;
259 }
260 i64::try_from(ic_platform_runtime::ic0::stable64_grow(pages)).unwrap_or(-1)
261 }
262
263 fn read(&self, offset: u64, bytes: &mut [u8]) {
264 if bytes.is_empty() {
267 admit_range(offset, 0, self.size());
268 return;
269 }
270 ic_platform_runtime::ic0::stable64_read(bytes, offset);
271 }
272
273 #[expect(
274 unsafe_code,
275 reason = "MaybeUninit exposes raw writable bytes to the safe Runtime boundary."
276 )]
277 unsafe fn read_unsafe(&self, offset: u64, dst: *mut u8, count: usize) {
278 if count == 0 {
279 admit_range(offset, 0, self.size());
280 return;
281 }
282 let bytes = unsafe {
287 std::slice::from_raw_parts_mut(dst.cast::<std::mem::MaybeUninit<u8>>(), count)
288 };
289 ic_platform_runtime::ic0::stable64_read_uninit(bytes, offset);
290 }
291
292 fn write(&self, offset: u64, bytes: &[u8]) {
293 if bytes.is_empty() {
296 admit_range(offset, 0, self.size());
297 return;
298 }
299 ic_platform_runtime::ic0::stable64_write(bytes, offset);
300 }
301}
302
303#[cfg(test)]
304mod contract_tests;