/home/runner/work/lyquor/lyquor/platform/vm/src/mem.rs
Line | Count | Source |
1 | | use std::marker::PhantomData; |
2 | | use std::sync::{Arc, atomic}; |
3 | | |
4 | | use crate::guest::VmGuest; |
5 | | use crate::{RwLock as SyncRwLock, RwLockWriteGuard as SyncRwLockWriteGuard}; |
6 | | use lyquid::mem::Wasm32; |
7 | | use lyquor_api::store::LazyBytes; |
8 | | use lyquor_primitives::StateCategory; |
9 | | use lyquor_state::{Key, State, StateR, Value}; |
10 | | use tokio::sync::{RwLock, RwLockWriteGuard}; |
11 | | use userspace_pagefault::{ |
12 | | self, AccessType, AsyncPageFaultFuture, AsyncPageStore, PageChunks, PagedSegment, SharedMemory, |
13 | | }; |
14 | | |
15 | | #[cfg(test)] use lyquor_test::test; |
16 | | |
17 | | const LYTEPAGE_SIZE: usize = 1024; // the logical page size for LyteMemory |
18 | | const BEGIN_GUARD_SIZE: usize = 2 << 30; |
19 | | const END_GUARD_SIZE: usize = 2 << 30; |
20 | | |
21 | 68 | fn lytememory_setup() { |
22 | | static INITIALIZED: std::sync::Once = std::sync::Once::new(); |
23 | 68 | INITIALIZED.call_once(|| {39 |
24 | | // this is a non-so-bad hack to wasmtime: creating a dummy wasmtime engine will trigger the |
25 | | // on-time initialization of the trap handler by wasmtime. Then we can override this trap |
26 | | // handler and only hand back when the signals are not triggered within any managed |
27 | | // LyteMemory. |
28 | 39 | let _ = wasmtime::Engine::new(wasmtime::Config::new().macos_use_mach_ports(false)); |
29 | 39 | }); |
30 | 68 | } |
31 | | |
32 | | struct Bitmask(Box<[atomic::AtomicU64]>); |
33 | | |
34 | | impl Bitmask { |
35 | 379 | fn new(size: usize) -> Self { |
36 | | Self( |
37 | 1.53M | std::iter::repeat_with379 (|| atomic::AtomicU64::new(0)) |
38 | 379 | .take((size + 63) >> 6) |
39 | 379 | .collect::<Vec<_>>() |
40 | 379 | .into(), |
41 | | ) |
42 | 379 | } |
43 | | |
44 | | #[inline(always)] |
45 | 1.29k | fn mark(&self, idx: usize) { |
46 | 1.29k | self.0[idx >> 6].fetch_or(1 << (idx & 63), atomic::Ordering::Relaxed); |
47 | 1.29k | } |
48 | | |
49 | | #[inline(always)] |
50 | 897 | fn is_marked(&self, idx: usize) -> bool { |
51 | 897 | (self.0[idx >> 6].fetch_or(0, atomic::Ordering::Relaxed) >> (idx & 63)) & 1 == 1 |
52 | 897 | } |
53 | | |
54 | | const TABLE: [u64; 64] = [ |
55 | | 0x00, 0x3a, 0x01, 0x3b, 0x2f, 0x35, 0x02, 0x3c, 0x27, 0x30, 0x1b, 0x36, 0x21, 0x2a, 0x03, 0x3d, 0x33, 0x25, |
56 | | 0x28, 0x31, 0x12, 0x1c, 0x14, 0x37, 0x1e, 0x22, 0x0b, 0x2b, 0x0e, 0x16, 0x04, 0x3e, 0x39, 0x2e, 0x34, 0x26, |
57 | | 0x1a, 0x20, 0x29, 0x32, 0x24, 0x11, 0x13, 0x1d, 0x0a, 0x0d, 0x15, 0x38, 0x2d, 0x19, 0x1f, 0x23, 0x10, 0x09, |
58 | | 0x0c, 0x2c, 0x18, 0x0f, 0x08, 0x17, 0x07, 0x06, 0x05, 0x3f, |
59 | | ]; |
60 | | |
61 | | /// Turn a power of 2 (>0) to the exponent. |
62 | | #[inline(always)] |
63 | 401 | fn fast_log(mut x: u64) -> u64 { |
64 | 401 | x |= x >> 1; |
65 | 401 | x |= x >> 2; |
66 | 401 | x |= x >> 4; |
67 | 401 | x |= x >> 8; |
68 | 401 | x |= x >> 16; |
69 | 401 | x |= x >> 32; |
70 | 401 | Self::TABLE[((x.wrapping_mul(0x03f6eaf2cd271461)) >> 58) as usize] |
71 | 401 | } |
72 | | |
73 | | #[inline] |
74 | 172 | fn get_marked_and_clear(&self) -> impl Iterator<Item = usize> + '_ { |
75 | 663k | self.0172 .iter172 ().zip172 ((0..172 ).step_by172 (64)).flat_map172 (|(bits, i)| { |
76 | 663k | let mut v = bits.swap(0, atomic::Ordering::Relaxed); |
77 | 664k | std::iter::from_fn663k (move || { |
78 | | // this lowbit finding algorithm is suitable for a sparse bitmask, which is the |
79 | | // case typically for LyteMemory dirty pages |
80 | 664k | let lowbit = v; |
81 | 664k | if lowbit == 0 { |
82 | 663k | None |
83 | | } else { |
84 | 401 | let lowbit = lowbit.isolate_lowest_one(); |
85 | 401 | v ^= lowbit; // clear the lowest bit |
86 | 401 | Some(i + Self::fast_log(lowbit) as usize) |
87 | | } |
88 | 664k | }) |
89 | 663k | }) |
90 | 172 | } |
91 | | } |
92 | | |
93 | | #[test] |
94 | | fn test_bitmask() { |
95 | | let all_bits: Vec<_> = (0..64).collect(); |
96 | | let cases = [ |
97 | | (0, &[][..]), |
98 | | (65, &[][..]), |
99 | | (64, &all_bits[..]), |
100 | | (64, &[0, 2, 4, 7, 11, 33, 63][..]), |
101 | | (1234, &[0, 32, 63, 64, 100, 256, 1024, 1233][..]), |
102 | | ]; |
103 | | for (size, pos) in cases { |
104 | | let bm = Bitmask::new(size); |
105 | | for i in pos { |
106 | | bm.mark(*i); |
107 | | bm.mark(*i); |
108 | | assert!(bm.is_marked(*i)); |
109 | | } |
110 | | assert_eq!(bm.get_marked_and_clear().collect::<Vec<_>>(), pos); |
111 | | assert_eq!(bm.get_marked_and_clear().next(), None); |
112 | | for i in pos { |
113 | | assert!(!bm.is_marked(*i)); |
114 | | } |
115 | | } |
116 | | } |
117 | | |
118 | 3.36k | fn litepage_state_key(litepage_id: u32) -> Key { |
119 | | static PREFIX: std::sync::OnceLock<Key> = std::sync::OnceLock::new(); |
120 | 3.36k | let key = PREFIX.get_or_init(|| lyquid::LYTEMEM_PAGE_PREFIX.into36 ()); |
121 | | // using big-endian here so the state debug will be easier to read |
122 | 3.36k | let id: LazyBytes = litepage_id.to_be_bytes().into(); |
123 | 3.36k | id.prepend(key) |
124 | 3.36k | } |
125 | | |
126 | | struct Segment { |
127 | | /// Dirty page bitmask. One position (bit) corresponds to one OS page. |
128 | | dirty: Bitmask, |
129 | | /// Loaded page bitmask. One position (bit) corresponds to one OS page. |
130 | | loaded: Bitmask, |
131 | | /// Size of an OS page on the host. |
132 | | page_size: usize, |
133 | | /// Offset of this segment from the beginning of the LytePage area. |
134 | | litepage_base: usize, |
135 | | /// Offest of this segment in the memory (PagedSegment). |
136 | | base: usize, |
137 | | /// Length of the segment. |
138 | | length: usize, |
139 | | } |
140 | | |
141 | | impl Segment { |
142 | 187 | fn new(base: usize, length: usize, litepage_base: usize, page_size: usize) -> Self { |
143 | | // align to the closest page |
144 | 187 | let bitmask_size = length.div_ceil(page_size); |
145 | 187 | Self { |
146 | 187 | dirty: Bitmask::new(bitmask_size), |
147 | 187 | loaded: Bitmask::new(bitmask_size), |
148 | 187 | page_size, |
149 | 187 | litepage_base, |
150 | 187 | base, |
151 | 187 | length, |
152 | 187 | } |
153 | 187 | } |
154 | | |
155 | | #[inline(always)] |
156 | 1.87k | fn addr_to_idx(&self, mem_addr: usize) -> usize { |
157 | 1.87k | (mem_addr - self.base) / self.page_size |
158 | 1.87k | } |
159 | | |
160 | | #[inline(always)] |
161 | 620 | fn mark_dirty(&self, mem_addr: usize) { |
162 | 620 | self.dirty.mark(self.addr_to_idx(mem_addr)); |
163 | 620 | } |
164 | | |
165 | | #[inline(always)] |
166 | 739 | fn is_loaded(&self, mem_addr: usize) -> bool { |
167 | 739 | self.loaded.is_marked(self.addr_to_idx(mem_addr)) |
168 | 739 | } |
169 | | |
170 | | #[inline(always)] |
171 | 518 | fn mark_loaded(&self, mem_addr: usize) { |
172 | 518 | self.loaded.mark(self.addr_to_idx(mem_addr)); |
173 | 518 | } |
174 | | |
175 | | /// Write the dirty pages to the key-value state and clear the dirty bitmask. |
176 | 160 | async fn writeback_changes<S: State>(&self, state: &SyncRwLock<S>, mem: &[u8]) { |
177 | 160 | let pending = { |
178 | 160 | let state = state.read(); |
179 | 160 | let mut pending = Vec::new(); |
180 | 318 | for ospage_id in self.dirty160 .get_marked_and_clear160 () { |
181 | 318 | let segment_off = ospage_id * self.page_size; |
182 | 318 | let litepage_start = (self.litepage_base + segment_off) / LYTEPAGE_SIZE; |
183 | 318 | let mem_addr = self.base + segment_off; |
184 | 318 | let ospage = &mem[mem_addr..mem_addr + self.page_size]; |
185 | 1.27k | for (i, litepage) in ospage318 .chunks318 (LYTEPAGE_SIZE).enumerate318 () { |
186 | 1.27k | let key = litepage_state_key((litepage_start + i) as u32); |
187 | 1.27k | let litepage: Value = litepage.to_vec().into(); |
188 | 1.27k | pending.push((key.clone(), litepage, state.get(key))); |
189 | 1.27k | } |
190 | | } |
191 | 160 | pending |
192 | | }; |
193 | | |
194 | 160 | let mut changes = Vec::new(); |
195 | 1.27k | for (key, litepage, old) in pending160 { |
196 | 1.27k | let changed = match old.await { |
197 | 63 | Some(old) => litepage.as_ref() != old.as_ref(), // if the page was previous persisted, compare |
198 | 1.10M | None => !litepage.as_ref().iter()1.20k .all1.20k (|&b| b == 0), // otherwise check if it's empty |
199 | | }; |
200 | 1.27k | if changed { |
201 | 313 | changes.push((key, litepage)); |
202 | 959 | } |
203 | | } |
204 | | |
205 | 160 | let mut state = state.write(); |
206 | 313 | for (key, litepage) in changes160 { |
207 | 313 | state.set(key, Some(litepage)); |
208 | 313 | } |
209 | 160 | } |
210 | | |
211 | | /// Revert the dirty pages to their original data and clear the dirty bitmask. |
212 | 2 | async fn drop_changes<S: State>(&self, state: &SyncRwLock<S>) -> Vec<(usize, Option<Value>)> { |
213 | 2 | let pending = { |
214 | 2 | let state = state.read(); |
215 | 2 | let mut pending = Vec::new(); |
216 | 4 | for ospage_id in self.dirty2 .get_marked_and_clear2 () { |
217 | 4 | let segment_off = ospage_id * self.page_size; |
218 | 4 | let litepage_start = (self.litepage_base + segment_off) / LYTEPAGE_SIZE; |
219 | 4 | let mem_addr = self.base + segment_off; |
220 | 16 | for i in 0..self.page_size / LYTEPAGE_SIZE4 { |
221 | 16 | let key = litepage_state_key((litepage_start + i) as u32); |
222 | 16 | pending.push((mem_addr + i * LYTEPAGE_SIZE, state.get(key))); |
223 | 16 | } |
224 | | } |
225 | 2 | pending |
226 | | }; |
227 | | |
228 | 2 | let mut restores = Vec::with_capacity(pending.len()); |
229 | 16 | for (mem_addr, old) in pending2 { |
230 | 16 | restores.push((mem_addr, old.await)); |
231 | | } |
232 | 2 | restores |
233 | 2 | } |
234 | | } |
235 | | |
236 | | #[derive(Clone)] |
237 | | struct InstanceSegment { |
238 | | shm: SharedMemory, |
239 | | seg: Arc<Segment>, |
240 | | parking: Arc<crate::sync::ParkingSpot>, |
241 | | } |
242 | | |
243 | | impl InstanceSegment { |
244 | 68 | fn new( |
245 | 68 | base: usize, size: usize, litepage_base: usize, page_size: usize, |
246 | 68 | ) -> Result<Self, userspace_pagefault::Error> { |
247 | | Ok(Self { |
248 | 68 | shm: SharedMemory::new(size)?0 , |
249 | 68 | seg: Arc::new(Segment::new(base, size, litepage_base, page_size)), |
250 | 68 | parking: Arc::new(crate::sync::ParkingSpot::new()), |
251 | | }) |
252 | 68 | } |
253 | | } |
254 | | |
255 | | #[derive(Clone)] |
256 | | struct Context { |
257 | | state: crate::instance::LyquidState, |
258 | | network: Arc<Segment>, |
259 | | instance: InstanceSegment, |
260 | | litepage: std::ops::Range<usize>, |
261 | | } |
262 | | |
263 | | impl AsyncPageStore for Context { |
264 | 3.09k | fn page_fault_async(&mut self, offset: usize, length: usize, access: AccessType) -> AsyncPageFaultFuture<'_> { |
265 | 3.09k | Box::pin(async move { |
266 | 3.09k | if !self.litepage.contains(&offset) { |
267 | | // the accessed page is outside LytePage (network/instance) address range. Returns None |
268 | | // because no loading is needed. |
269 | 2.35k | return None; |
270 | 739 | } |
271 | | |
272 | | /* |
273 | | println!( |
274 | | "{:?} pagefault {:?} @ {:x}", |
275 | | self as *mut Self, |
276 | | access, |
277 | | offset - BEGIN_GUARD_SIZE |
278 | | ); |
279 | | */ |
280 | | |
281 | | // the loaded OS page may span across multiple LytePages. |
282 | 739 | let litepage_start = (offset - self.litepage.start) / LYTEPAGE_SIZE; |
283 | 518 | let (seg, reads) = { |
284 | | // check which segment this address accesses. |
285 | 739 | let (state, seg) = match offset { |
286 | 739 | off314 if off < self.instance.seg.base => (self.state.network.read(), &self.network)314 , |
287 | 425 | _ => (self.state.instance.read(), &self.instance.seg), |
288 | | }; |
289 | 739 | if let AccessType::Write = access { |
290 | 620 | // mark the page as dirty |
291 | 620 | seg.mark_dirty(offset); |
292 | 620 | }119 |
293 | 739 | if seg.is_loaded(offset) { |
294 | | // the page was already loaded |
295 | 221 | return None; |
296 | 518 | } |
297 | | |
298 | | //println!("loading @ {:x}", offset - BEGIN_GUARD_SIZE); |
299 | | |
300 | 518 | let reads = (0..length / LYTEPAGE_SIZE) |
301 | 2.07k | .map518 (|i| state.get(litepage_state_key((litepage_start + i) as u32))) |
302 | 518 | .collect::<Vec<_>>(); |
303 | 518 | (seg, reads) |
304 | | }; |
305 | | |
306 | | // collect the contents of all LytePages read from state store. |
307 | 518 | let mut chunks = Vec::with_capacity(reads.len()); |
308 | 2.07k | for read in reads518 { |
309 | 2.07k | chunks.push(Box::new(read.await.unwrap_or_else(|| {2.01k |
310 | 2.01k | std::iter::repeat_with(|| 0) |
311 | 2.01k | .take(LYTEPAGE_SIZE) |
312 | 2.01k | .collect::<Vec<u8>>() |
313 | 2.01k | .into() |
314 | 2.07k | }2.01k )) as Box<dyn AsRef<[u8]>>); |
315 | | } |
316 | 518 | seg.mark_loaded(offset); |
317 | 518 | Some(Box::new(chunks.into_iter()) as PageChunks<'_>) |
318 | 3.09k | }) |
319 | 3.09k | } |
320 | | } |
321 | | |
322 | | /// Factory and shared backing resources for Lyquid virtual memory objects. |
323 | | pub struct LyteMemoryManager { |
324 | | instance: InstanceSegment, |
325 | | instance_mapped: userspace_pagefault::Segment, |
326 | | network_range: std::ops::Range<usize>, |
327 | | litepage: std::ops::Range<usize>, |
328 | | page_size: usize, |
329 | | total: usize, |
330 | | } |
331 | | |
332 | | impl LyteMemoryManager { |
333 | | /// Create the shared instance segment and reserve the LyteMemory virtual address range. |
334 | 68 | pub fn new() -> Result<Self, userspace_pagefault::Error> { |
335 | 68 | lytememory_setup(); |
336 | 68 | let wasm_usable = lyquid::LYTEMEM_SIZE_IN_MB << 20; |
337 | 68 | let network = lyquid::NETWORK_MEMSIZE_IN_MB << 20; |
338 | 68 | let instance = lyquid::INSTANCE_MEMSIZE_IN_MB << 20; |
339 | | // WASM 32-bit starting address for LytePage area |
340 | 68 | let start = BEGIN_GUARD_SIZE + lyquid::LYTEMEM_BASE; |
341 | | // WASM 32-bit ending address for LytePage area |
342 | 68 | let end = start + instance + network; |
343 | | // total virtual size, including guards |
344 | 68 | let total = BEGIN_GUARD_SIZE + wasm_usable + END_GUARD_SIZE; |
345 | 68 | assert!(end <= total); |
346 | 68 | let page_size = userspace_pagefault::get_page_size()?0 ; |
347 | 68 | let network_range = start..start + network; |
348 | 68 | let instance_range = start + network..end; |
349 | | // all LyteMemories generated from this LyteMemoryManager will share the same |
350 | | // InstanceSegment |
351 | 68 | let instance = InstanceSegment::new( |
352 | 68 | instance_range.start, |
353 | 68 | instance_range.len(), |
354 | 68 | network_range.len(), |
355 | 68 | page_size, |
356 | 0 | )?; |
357 | | |
358 | 68 | let instance_mapped = |
359 | 68 | userspace_pagefault::Segment::new(None, total, page_size, userspace_pagefault::ProtFlags::PROT_NONE)?0 ; |
360 | 68 | instance_mapped.make_shared( |
361 | 68 | instance.seg.base, |
362 | 68 | &instance.shm, |
363 | 68 | userspace_pagefault::ProtFlags::PROT_READ | userspace_pagefault::ProtFlags::PROT_WRITE, |
364 | 0 | )?; |
365 | | |
366 | 68 | Ok(Self { |
367 | 68 | instance, |
368 | 68 | instance_mapped, |
369 | 68 | network_range, |
370 | 68 | litepage: start..end, |
371 | 68 | page_size, |
372 | 68 | total, |
373 | 68 | }) |
374 | 68 | } |
375 | | |
376 | | /// Flush the dirty pages in the instance segement to the key-value state and clear the dirty |
377 | | /// bitmask. |
378 | 64 | pub(crate) async fn flush_instance_state<'a, T, S: State>( |
379 | 64 | &self, lock: &'a RwLock<T>, state: &'a SyncRwLock<S>, |
380 | 64 | ) -> (RwLockWriteGuard<'a, T>, SyncRwLockWriteGuard<'a, S>) { |
381 | 64 | let guard = lock.write().await; |
382 | 64 | self.instance |
383 | 64 | .seg |
384 | 64 | .writeback_changes(state, self.instance_mapped.as_slice()) |
385 | 64 | .await; |
386 | 64 | let state = state.write(); |
387 | 64 | (guard, state) |
388 | 64 | } |
389 | | |
390 | | /// Create a new LyteMemory object. The object can be clone-shared but the underlying resource |
391 | | /// is the same. |
392 | 119 | pub fn new_lytememory<G: VmGuest>( |
393 | 119 | &self, state: crate::instance::LyquidState, category: StateCategory, |
394 | 119 | ) -> Result<LyteMemory<G>, userspace_pagefault::Error> { |
395 | 119 | let ctx = Context { |
396 | 119 | state, |
397 | 119 | network: Arc::new(Segment::new( |
398 | 119 | self.network_range.start, |
399 | 119 | self.network_range.len(), |
400 | 119 | 0, |
401 | 119 | self.page_size, |
402 | 119 | )), |
403 | 119 | instance: self.instance.clone(), |
404 | 119 | litepage: self.litepage.clone(), |
405 | 119 | }; |
406 | | |
407 | 119 | let mem = Arc::new(PagedSegment::new_async(self.total, ctx.clone(), None)?0 ); |
408 | 119 | mem.make_shared(self.instance.seg.base, &self.instance.shm)?0 ; |
409 | | |
410 | 119 | Ok(LyteMemory { |
411 | 119 | mem, |
412 | 119 | ctx, |
413 | 119 | category, |
414 | 119 | guest: PhantomData, |
415 | 119 | }) |
416 | 119 | } |
417 | | } |
418 | | |
419 | | /// A sharable object that represents a userspace-paged virtual memory used by LVM. |
420 | | #[derive(Clone)] |
421 | | pub struct LyteMemory<G: VmGuest = Wasm32> { |
422 | | mem: Arc<PagedSegment<'static>>, |
423 | | ctx: Context, |
424 | | category: StateCategory, |
425 | | guest: PhantomData<G>, |
426 | | } |
427 | | |
428 | | impl<G: VmGuest> LyteMemory<G> { |
429 | | /// Reset the dirty-page detection of the instance segment. This is required after a flush |
430 | | /// write-back of the instance segment, otherwise future changes will not be detected. |
431 | 0 | pub fn reset_instance_write_detection(&self) { |
432 | 0 | self.mem |
433 | 0 | .reset_write_detection(self.ctx.instance.seg.base, self.ctx.instance.seg.length) |
434 | 0 | .expect("Failed to reset write detection."); // FIXME: handle this error |
435 | 0 | } |
436 | | |
437 | | /// Flush the dirty pages of the network segment to the key-value state carried by LyteMemory. |
438 | | /// The dirty-page detection is also reset properly after flushing the pages. |
439 | 96 | pub async fn flush_network_state(&self) { |
440 | 96 | self.ctx |
441 | 96 | .network |
442 | 96 | .writeback_changes(self.ctx.state.network.as_ref(), self.mem.as_slice()) |
443 | 96 | .await; |
444 | 96 | self.mem |
445 | 96 | .reset_write_detection(self.ctx.network.base, self.ctx.network.length) |
446 | 96 | .expect("Failed to reset write detection."); // FIXME: handle this error |
447 | | // the lock is released at the end |
448 | 96 | } |
449 | | |
450 | | /// Revert all dirty pages of the network segment. |
451 | | /// The dirty-page detection is also reset properly after flushing the pages. |
452 | 2 | pub async fn revert_network_state(&self) { |
453 | 2 | let restores = self.ctx.network.drop_changes(self.ctx.state.network.as_ref()).await; |
454 | 2 | let (base, len) = self.mem.as_raw_parts(); |
455 | 2 | let mem = unsafe { std::slice::from_raw_parts_mut(base, len) }; |
456 | 16 | for (mem_addr, old) in restores2 { |
457 | 16 | let litepage = &mut mem[mem_addr..mem_addr + LYTEPAGE_SIZE]; |
458 | 16 | match old { |
459 | 4 | Some(old) => litepage.copy_from_slice(&old), |
460 | 12 | None => litepage.fill(0), |
461 | | } |
462 | | } |
463 | 2 | self.mem |
464 | 2 | .reset_write_detection(self.ctx.network.base, self.ctx.network.length) |
465 | 2 | .expect("Failed to reset write detection."); // FIXME: handle this error |
466 | | // the lock is released at the end |
467 | 2 | } |
468 | | |
469 | | /// Get the raw pointer to the host memory at a given WASM address. |
470 | | #[inline(always)] |
471 | 11.9k | pub fn host_ptr_by_wasm_addr(&self, addr: G::Usize) -> *mut u8 { |
472 | 11.9k | let (base, _) = self.mem.as_raw_parts(); |
473 | 11.9k | base.wrapping_add(G::usize_to_host(addr) + BEGIN_GUARD_SIZE) |
474 | 11.9k | } |
475 | | |
476 | | /// Get a read-only slice of memory by WASM address. |
477 | 4.55k | pub fn slice_by_wasm_addr(&self, offset: G::Usize, length: G::Usize) -> &[u8] { |
478 | 4.55k | unsafe { std::slice::from_raw_parts(self.host_ptr_by_wasm_addr(offset), G::usize_to_host(length)) } |
479 | 4.55k | } |
480 | | |
481 | | /// Get a mutable slice of the memory with WASM address. |
482 | | /// |
483 | | /// # Safety |
484 | | /// |
485 | | /// The caller must ensure no other live references alias the returned range for the duration |
486 | | /// of the returned slice. |
487 | | #[allow(clippy::mut_from_ref)] |
488 | 7.26k | pub unsafe fn slice_by_wasm_addr_mut(&self, offset: G::Usize, length: G::Usize) -> &mut [u8] { |
489 | 7.26k | unsafe { std::slice::from_raw_parts_mut(self.host_ptr_by_wasm_addr(offset), G::usize_to_host(length)) } |
490 | 7.26k | } |
491 | | |
492 | | /// Get the function category of this LyteMemory's owner. |
493 | 803 | pub fn category(&self) -> StateCategory { |
494 | 803 | self.category |
495 | 803 | } |
496 | | |
497 | | /// Return the wait/notify queue associated with the shared instance segment. |
498 | 238 | pub(crate) fn instance_parking_spot(&self) -> Arc<crate::sync::ParkingSpot> { |
499 | 238 | self.ctx.instance.parking.clone() |
500 | 238 | } |
501 | | } |
502 | | |
503 | | struct WasmRuntimeMemory<G: VmGuest> { |
504 | | inner: LyteMemory<G>, |
505 | | size: usize, |
506 | | } |
507 | | |
508 | | unsafe impl<G: VmGuest> wasmtime::LinearMemory for WasmRuntimeMemory<G> { |
509 | 238 | fn byte_size(&self) -> usize { |
510 | 238 | self.size |
511 | 238 | } |
512 | | |
513 | 0 | fn byte_capacity(&self) -> usize { |
514 | 0 | self.size |
515 | 0 | } |
516 | | |
517 | 0 | fn grow_to(&mut self, size: usize) -> wasmtime::Result<()> { |
518 | 0 | if size > self.size { |
519 | 0 | return Err(wasmtime::Error::msg("LyteMemory is not growable.")); |
520 | 0 | } |
521 | 0 | Ok(()) |
522 | 0 | } |
523 | | |
524 | 119 | fn as_ptr(&self) -> *mut u8 { |
525 | 119 | let (ptr, _) = self.inner.mem.as_raw_parts(); |
526 | 119 | unsafe { ptr.add(BEGIN_GUARD_SIZE) } |
527 | 119 | } |
528 | | } |
529 | | |
530 | | /// Memory factory that makes wasmtime memory for the given LyteMemory. |
531 | | pub struct WasmMemoryCreator<G: VmGuest = Wasm32>(pub LyteMemory<G>); |
532 | | |
533 | | unsafe impl<G: VmGuest> wasmtime::MemoryCreator for WasmMemoryCreator<G> { |
534 | 119 | fn new_memory( |
535 | 119 | &self, _mt: wasmtime::MemoryType, _min: usize, _max: Option<usize>, _reserved: Option<usize>, _guard: usize, |
536 | 119 | ) -> Result<Box<dyn wasmtime::LinearMemory>, String> { |
537 | 119 | Ok(Box::new(WasmRuntimeMemory { |
538 | 119 | inner: self.0.clone(), |
539 | 119 | size: lyquid::LYTEMEM_SIZE_IN_MB << 20, |
540 | 119 | })) |
541 | 119 | } |
542 | | } |