/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::{Mutex, 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 | 41 | fn lytememory_setup() { |
22 | | static INITIALIZED: std::sync::Once = std::sync::Once::new(); |
23 | 41 | INITIALIZED.call_once(|| {19 |
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 | 19 | let _ = wasmtime::Engine::new(wasmtime::Config::new().macos_use_mach_ports(false)); |
29 | 19 | }) |
30 | 41 | } |
31 | | |
32 | | struct Bitmask(Box<[atomic::AtomicU64]>); |
33 | | |
34 | | impl Bitmask { |
35 | 225 | fn new(size: usize) -> Self { |
36 | | Self( |
37 | 909k | std::iter::repeat_with225 (|| atomic::AtomicU64::new(0)) |
38 | 225 | .take((size + 63) >> 6) |
39 | 225 | .collect::<Vec<_>>() |
40 | 225 | .into(), |
41 | | ) |
42 | 225 | } |
43 | | |
44 | 0 | fn clear(&self) { |
45 | 0 | for bits in self.0.iter() { |
46 | 0 | bits.store(0, atomic::Ordering::Relaxed) |
47 | | } |
48 | 0 | } |
49 | | |
50 | | #[inline(always)] |
51 | 562 | fn mark(&self, idx: usize) { |
52 | 562 | self.0[idx >> 6].fetch_or(1 << (idx & 63), atomic::Ordering::Relaxed); |
53 | 562 | } |
54 | | |
55 | | #[inline(always)] |
56 | 363 | fn is_marked(&self, idx: usize) -> bool { |
57 | 363 | (self.0[idx >> 6].fetch_or(0, atomic::Ordering::Relaxed) >> (idx & 63)) & 1 == 1 |
58 | 363 | } |
59 | | |
60 | | const TABLE: [u64; 64] = [ |
61 | | 0x00, 0x3a, 0x01, 0x3b, 0x2f, 0x35, 0x02, 0x3c, 0x27, 0x30, 0x1b, 0x36, 0x21, 0x2a, 0x03, 0x3d, 0x33, 0x25, |
62 | | 0x28, 0x31, 0x12, 0x1c, 0x14, 0x37, 0x1e, 0x22, 0x0b, 0x2b, 0x0e, 0x16, 0x04, 0x3e, 0x39, 0x2e, 0x34, 0x26, |
63 | | 0x1a, 0x20, 0x29, 0x32, 0x24, 0x11, 0x13, 0x1d, 0x0a, 0x0d, 0x15, 0x38, 0x2d, 0x19, 0x1f, 0x23, 0x10, 0x09, |
64 | | 0x0c, 0x2c, 0x18, 0x0f, 0x08, 0x17, 0x07, 0x06, 0x05, 0x3f, |
65 | | ]; |
66 | | |
67 | | /// Turn a power of 2 (>0) to the exponent. |
68 | | #[inline(always)] |
69 | 311 | fn fast_log(mut x: u64) -> u64 { |
70 | 311 | x |= x >> 1; |
71 | 311 | x |= x >> 2; |
72 | 311 | x |= x >> 4; |
73 | 311 | x |= x >> 8; |
74 | 311 | x |= x >> 16; |
75 | 311 | x |= x >> 32; |
76 | 311 | Self::TABLE[((x.wrapping_mul(0x03f6eaf2cd271461)) >> 58) as usize] |
77 | 311 | } |
78 | | |
79 | | #[inline] |
80 | 120 | fn get_marked_and_clear(&self) -> impl Iterator<Item = usize> + '_ { |
81 | 479k | self.0120 .iter120 ().zip120 ((0..120 ).step_by120 (64)).flat_map120 (|(bits, i)| { |
82 | 479k | let mut v = bits.swap(0, atomic::Ordering::Relaxed); |
83 | 479k | std::iter::from_fn479k (move || { |
84 | | // this lowbit finding algorithm is suitable for a sparse bitmask, which is the |
85 | | // case typically for LyteMemory dirty pages |
86 | 479k | let mut lowbit = v; |
87 | 479k | if lowbit == 0 { |
88 | 479k | None |
89 | | } else { |
90 | 311 | lowbit = lowbit & lowbit.wrapping_neg(); |
91 | 311 | v ^= lowbit; // clear the lowest bit |
92 | 311 | Some(i + Self::fast_log(lowbit) as usize) |
93 | | } |
94 | 479k | }) |
95 | 479k | }) |
96 | 120 | } |
97 | | } |
98 | | |
99 | | #[test] |
100 | | fn test_bitmask() { |
101 | | let all_bits: Vec<_> = (0..64).collect(); |
102 | | let cases = [ |
103 | | (64, &all_bits[..]), |
104 | | (64, &[0, 2, 4, 7, 11, 33, 63][..]), |
105 | | (1234, &[0, 32, 63, 64, 100, 256, 1024, 1233][..]), |
106 | | ]; |
107 | | for (size, pos) in cases { |
108 | | let bm = Bitmask::new(size); |
109 | | for i in pos.iter() { |
110 | | bm.mark(*i); |
111 | | } |
112 | | for (i, j) in bm.get_marked_and_clear().zip(pos.iter()) { |
113 | | assert_eq!(i, *j); |
114 | | } |
115 | | } |
116 | | } |
117 | | |
118 | 1.68k | fn litepage_state_key(litepage_id: u32) -> Key { |
119 | | static PREFIX: std::sync::OnceLock<Key> = std::sync::OnceLock::new(); |
120 | 1.68k | let key = PREFIX.get_or_init(|| lyquid::LYTEMEM_PAGE_PREFIX.into18 ()); |
121 | | // using big-endian here so the state debug will be easier to read |
122 | 1.68k | let id: LazyBytes = litepage_id.to_be_bytes().into(); |
123 | 1.68k | id.prepend(key) |
124 | 1.68k | } |
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 | 111 | fn new(base: usize, length: usize, litepage_base: usize, page_size: usize) -> Self { |
143 | | // align to the closest page |
144 | 111 | let bitmask_size = length.div_ceil(page_size); |
145 | 111 | Self { |
146 | 111 | dirty: Bitmask::new(bitmask_size), |
147 | 111 | loaded: Bitmask::new(bitmask_size), |
148 | 111 | page_size, |
149 | 111 | litepage_base, |
150 | 111 | base, |
151 | 111 | length, |
152 | 111 | } |
153 | 111 | } |
154 | | |
155 | | #[inline(always)] |
156 | 846 | fn addr_to_idx(&self, mem_addr: usize) -> usize { |
157 | 846 | (mem_addr - self.base) / self.page_size |
158 | 846 | } |
159 | | |
160 | | #[inline(always)] |
161 | 293 | fn mark_dirty(&self, mem_addr: usize) { |
162 | 293 | self.dirty.mark(self.addr_to_idx(mem_addr)) |
163 | 293 | } |
164 | | |
165 | | #[inline(always)] |
166 | 363 | fn is_loaded(&self, mem_addr: usize) -> bool { |
167 | 363 | self.loaded.is_marked(self.addr_to_idx(mem_addr)) |
168 | 363 | } |
169 | | |
170 | | #[inline(always)] |
171 | 190 | fn mark_loaded(&self, mem_addr: usize) { |
172 | 190 | self.loaded.mark(self.addr_to_idx(mem_addr)) |
173 | 190 | } |
174 | | |
175 | | /// Write the dirty pages to the key-value state and clear the dirty bitmask. |
176 | 115 | async fn writeback_changes<S: State>(&self, state: &SyncRwLock<S>, mem: &[u8]) { |
177 | 115 | let pending = { |
178 | 115 | let state = state.read(); |
179 | 115 | let mut pending = Vec::new(); |
180 | 230 | for ospage_id in self.dirty115 .get_marked_and_clear115 () { |
181 | 230 | let segment_off = ospage_id * self.page_size; |
182 | 230 | let litepage_start = (self.litepage_base + segment_off) / LYTEPAGE_SIZE; |
183 | 230 | let mem_addr = self.base + segment_off; |
184 | 230 | let ospage = &mem[mem_addr..mem_addr + self.page_size]; |
185 | 920 | for (i, litepage) in ospage230 .chunks230 (LYTEPAGE_SIZE).enumerate230 () { |
186 | 920 | let key = litepage_state_key((litepage_start + i) as u32); |
187 | 920 | let litepage: Value = litepage.to_vec().into(); |
188 | 920 | pending.push((key.clone(), litepage, state.get(key))); |
189 | 920 | } |
190 | | } |
191 | 115 | pending |
192 | | }; |
193 | | |
194 | 115 | let mut changes = Vec::new(); |
195 | 920 | for (key, litepage, old) in pending115 { |
196 | 920 | let changed = match old.await { |
197 | 71 | Some(old) => litepage.as_ref() != old.as_ref(), // if the page was previous persisted, compare |
198 | 782k | None => !litepage.as_ref().iter()849 .all849 (|&b| b == 0), // otherwise check if it's empty |
199 | | }; |
200 | 920 | if changed { |
201 | 222 | changes.push((key, litepage)); |
202 | 698 | } |
203 | | } |
204 | | |
205 | 115 | let mut state = state.write(); |
206 | 222 | for (key, litepage) in changes115 { |
207 | 222 | state.set(key, Some(litepage)); |
208 | 222 | } |
209 | 115 | } |
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 | 2 | for ospage_id in self.dirty.get_marked_and_clear() { |
217 | 2 | let segment_off = ospage_id * self.page_size; |
218 | 2 | let litepage_start = (self.litepage_base + segment_off) / LYTEPAGE_SIZE; |
219 | 2 | let mem_addr = self.base + segment_off; |
220 | 8 | for i in 0..self.page_size / LYTEPAGE_SIZE2 { |
221 | 8 | let key = litepage_state_key((litepage_start + i) as u32); |
222 | 8 | pending.push((mem_addr + i * LYTEPAGE_SIZE, state.get(key))); |
223 | 8 | } |
224 | | } |
225 | 2 | pending |
226 | | }; |
227 | | |
228 | 2 | let mut restores = Vec::with_capacity(pending.len()); |
229 | 8 | for (mem_addr, old) in pending2 { |
230 | 8 | 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 | 41 | fn new( |
245 | 41 | base: usize, size: usize, litepage_base: usize, page_size: usize, |
246 | 41 | ) -> Result<Self, userspace_pagefault::Error> { |
247 | | Ok(Self { |
248 | 41 | shm: SharedMemory::new(size)?0 , |
249 | 41 | seg: Arc::new(Segment::new(base, size, litepage_base, page_size)), |
250 | 41 | parking: Arc::new(crate::sync::ParkingSpot::new()), |
251 | | }) |
252 | 41 | } |
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 | 874 | fn page_fault_async(&mut self, offset: usize, length: usize, access: AccessType) -> AsyncPageFaultFuture<'_> { |
265 | 874 | Box::pin(async move { |
266 | 874 | 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 | 511 | return None; |
270 | 363 | } |
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 | 363 | let litepage_start = (offset - self.litepage.start) / LYTEPAGE_SIZE; |
283 | 190 | let (seg, reads) = { |
284 | | // check which segment this address accesses. |
285 | 363 | let (state, seg) = match offset { |
286 | 363 | off223 if off < self.instance.seg.base => (self.state.network.read(), &self.network)223 , |
287 | 140 | _ => (self.state.instance.read(), &self.instance.seg), |
288 | | }; |
289 | 363 | if let AccessType::Write = access { |
290 | | // mark the page as dirty |
291 | 293 | seg.mark_dirty(offset) |
292 | 70 | } |
293 | 363 | if seg.is_loaded(offset) { |
294 | | // the page was already loaded |
295 | 173 | return None; |
296 | 190 | } |
297 | | |
298 | | //println!("loading @ {:x}", offset - BEGIN_GUARD_SIZE); |
299 | | |
300 | 190 | let reads = (0..length / LYTEPAGE_SIZE) |
301 | 760 | .map190 (|i| state.get(litepage_state_key((litepage_start + i) as u32))) |
302 | 190 | .collect::<Vec<_>>(); |
303 | 190 | (seg, reads) |
304 | | }; |
305 | | |
306 | | // collect the contents of all LytePages read from state store. |
307 | 190 | let mut chunks = Vec::with_capacity(reads.len()); |
308 | 760 | for read in reads190 { |
309 | 760 | chunks.push(Box::new(read.await.unwrap_or_else(|| {724 |
310 | 724 | std::iter::repeat_with(|| 0) |
311 | 724 | .take(LYTEPAGE_SIZE) |
312 | 724 | .collect::<Vec<u8>>() |
313 | 724 | .into() |
314 | 760 | }724 )) as Box<dyn AsRef<[u8]>>); |
315 | | } |
316 | 190 | seg.mark_loaded(offset); |
317 | 190 | Some(Box::new(chunks.into_iter()) as PageChunks<'_>) |
318 | 874 | }) |
319 | 874 | } |
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 | 41 | pub fn new() -> Result<Self, userspace_pagefault::Error> { |
335 | 41 | lytememory_setup(); |
336 | 41 | let wasm_usable = lyquid::LYTEMEM_SIZE_IN_MB << 20; |
337 | 41 | let network = lyquid::NETWORK_MEMSIZE_IN_MB << 20; |
338 | 41 | let instance = lyquid::INSTANCE_MEMSIZE_IN_MB << 20; |
339 | | // WASM 32-bit starting address for LytePage area |
340 | 41 | let start = BEGIN_GUARD_SIZE + lyquid::LYTEMEM_BASE; |
341 | | // WASM 32-bit ending address for LytePage area |
342 | 41 | let end = start + instance + network; |
343 | | // total virtual size, including guards |
344 | 41 | let total = BEGIN_GUARD_SIZE + wasm_usable + END_GUARD_SIZE; |
345 | 41 | assert!(end <= total); |
346 | 41 | let page_size = userspace_pagefault::get_page_size()?0 ; |
347 | 41 | let network_range = start..start + network; |
348 | 41 | let instance_range = start + network..end; |
349 | | // all LyteMemories generated from this LyteMemoryManager will share the same |
350 | | // InstanceSegment |
351 | 41 | let instance = InstanceSegment::new( |
352 | 41 | instance_range.start, |
353 | 41 | instance_range.len(), |
354 | 41 | network_range.len(), |
355 | 41 | page_size, |
356 | 0 | )?; |
357 | | |
358 | 41 | let instance_mapped = |
359 | 41 | userspace_pagefault::Segment::new(None, total, page_size, userspace_pagefault::ProtFlags::PROT_NONE)?0 ; |
360 | 41 | instance_mapped.make_shared( |
361 | 41 | instance.seg.base, |
362 | 41 | &instance.shm, |
363 | 41 | userspace_pagefault::ProtFlags::PROT_READ | userspace_pagefault::ProtFlags::PROT_WRITE, |
364 | 0 | )?; |
365 | | |
366 | 41 | Ok(Self { |
367 | 41 | instance, |
368 | 41 | instance_mapped, |
369 | 41 | network_range, |
370 | 41 | litepage: start..end, |
371 | 41 | page_size, |
372 | 41 | total, |
373 | 41 | }) |
374 | 41 | } |
375 | | |
376 | | /// Flush the dirty pages in the instance segement to the key-value state and clear the dirty |
377 | | /// bitmask. |
378 | 40 | pub(crate) async fn flush_instance_state<'a, T, S: State>( |
379 | 40 | &self, lock: &'a RwLock<T>, state: &'a SyncRwLock<S>, |
380 | 40 | ) -> (RwLockWriteGuard<'a, T>, SyncRwLockWriteGuard<'a, S>) { |
381 | 40 | let guard = lock.write().await; |
382 | 40 | self.instance |
383 | 40 | .seg |
384 | 40 | .writeback_changes(state, self.instance_mapped.as_slice()) |
385 | 40 | .await; |
386 | 40 | let state = state.write(); |
387 | 40 | (guard, state) |
388 | 40 | } |
389 | | |
390 | | /// Create a new LyteMemory object. The object can be clone-shared but the underlying resource |
391 | | /// is the same. |
392 | 70 | pub fn new_lytememory<G: VmGuest>( |
393 | 70 | &self, state: crate::instance::LyquidState, category: StateCategory, |
394 | 70 | ) -> Result<LyteMemory<G>, userspace_pagefault::Error> { |
395 | 70 | let ctx = Context { |
396 | 70 | state, |
397 | 70 | network: Arc::new(Segment::new( |
398 | 70 | self.network_range.start, |
399 | 70 | self.network_range.len(), |
400 | 70 | 0, |
401 | 70 | self.page_size, |
402 | 70 | )), |
403 | 70 | instance: self.instance.clone(), |
404 | 70 | litepage: self.litepage.clone(), |
405 | 70 | }; |
406 | | |
407 | 70 | let mem = Arc::new(PagedSegment::new_async(self.total, ctx.clone(), None)?0 ); |
408 | 70 | mem.make_shared(self.instance.seg.base, &self.instance.shm)?0 ; |
409 | | |
410 | 70 | Ok(LyteMemory { |
411 | 70 | mem, |
412 | 70 | ctx, |
413 | 70 | init: Arc::new(Mutex::new(false)), |
414 | 70 | category, |
415 | 70 | guest: PhantomData, |
416 | 70 | }) |
417 | 70 | } |
418 | | } |
419 | | |
420 | | /// A sharable object that represents a userspace-paged virtual memory used by LVM. |
421 | | #[derive(Clone)] |
422 | | pub struct LyteMemory<G: VmGuest = Wasm32> { |
423 | | mem: Arc<PagedSegment<'static>>, |
424 | | ctx: Context, |
425 | | init: Arc<Mutex<bool>>, |
426 | | category: StateCategory, |
427 | | guest: PhantomData<G>, |
428 | | } |
429 | | |
430 | | /// Guard returned to the caller that wins one-time LyteMemory initialization. |
431 | | #[allow(unused)] |
432 | | pub struct LyteMemoryInitGuard<'a>(tokio::sync::MutexGuard<'a, bool>); |
433 | | |
434 | | impl<G: VmGuest> LyteMemory<G> { |
435 | | /// Reset the dirty-page detection of the instance segment. This is required after a flush |
436 | | /// write-back of the instance segment, otherwise future changes will not be detected. |
437 | 0 | pub fn reset_instance_write_detection(&self) { |
438 | 0 | self.mem |
439 | 0 | .reset_write_detection(self.ctx.instance.seg.base, self.ctx.instance.seg.length) |
440 | 0 | .expect("Failed to reset write detection."); // FIXME: handle this error |
441 | 0 | } |
442 | | |
443 | | /// Flush the dirty pages of the network segment to the key-value state carried by LyteMemory. |
444 | | /// The dirty-page detection is also reset properly after flushing the pages. |
445 | 75 | pub async fn flush_network_state(&self) { |
446 | 75 | self.ctx |
447 | 75 | .network |
448 | 75 | .writeback_changes(self.ctx.state.network.as_ref(), self.mem.as_slice()) |
449 | 75 | .await; |
450 | 75 | self.mem |
451 | 75 | .reset_write_detection(self.ctx.network.base, self.ctx.network.length) |
452 | 75 | .expect("Failed to reset write detection."); // FIXME: handle this error |
453 | | // the lock is released at the end |
454 | 75 | } |
455 | | |
456 | | /// Revert all dirty pages of the network segment. |
457 | | /// The dirty-page detection is also reset properly after flushing the pages. |
458 | 2 | pub async fn revert_network_state(&self) { |
459 | 2 | let restores = self.ctx.network.drop_changes(self.ctx.state.network.as_ref()).await; |
460 | 2 | let (base, len) = self.mem.as_raw_parts(); |
461 | 2 | let mem = unsafe { std::slice::from_raw_parts_mut(base, len) }; |
462 | 8 | for (mem_addr, old) in restores2 { |
463 | 8 | let litepage = &mut mem[mem_addr..mem_addr + LYTEPAGE_SIZE]; |
464 | 8 | match old { |
465 | 2 | Some(old) => litepage.copy_from_slice(&old), |
466 | 6 | None => litepage.fill(0), |
467 | | } |
468 | | } |
469 | 2 | self.mem |
470 | 2 | .reset_write_detection(self.ctx.network.base, self.ctx.network.length) |
471 | 2 | .expect("Failed to reset write detection."); // FIXME: handle this error |
472 | | // the lock is released at the end |
473 | 2 | } |
474 | | |
475 | | /// Release all in-memory changes and allocated pages. |
476 | | #[allow(dead_code)] |
477 | 0 | pub async fn release(&self) -> Result<(), userspace_pagefault::Error> { |
478 | 0 | self.mem.release_all_pages()?; |
479 | 0 | self.mem |
480 | 0 | .make_shared(self.ctx.instance.seg.base, &self.ctx.instance.shm)?; |
481 | 0 | self.ctx.network.dirty.clear(); |
482 | 0 | self.ctx.network.loaded.clear(); |
483 | 0 | *self.init.lock().await = false; |
484 | 0 | Ok(()) |
485 | 0 | } |
486 | | |
487 | | /// Returns if the memory needs to be initialized. |
488 | 340 | pub async fn need_init(&self) -> Option<LyteMemoryInitGuard<'_>> { |
489 | 340 | let mut init = self.init.lock().await; |
490 | 340 | match *init { |
491 | 230 | true => None, |
492 | | false => { |
493 | | // only initialize once |
494 | 110 | *init = true; |
495 | 110 | Some(LyteMemoryInitGuard(init)) |
496 | | } |
497 | | } |
498 | 340 | } |
499 | | |
500 | | /// Manually set the initialized flag. |
501 | 40 | pub async fn set_init(&self, flag: bool) { |
502 | 40 | *self.init.lock().await = flag |
503 | 40 | } |
504 | | |
505 | | /// Get the raw pointer to the host memory at a given WASM address. |
506 | | #[inline(always)] |
507 | 5.41k | pub fn host_ptr_by_wasm_addr(&self, addr: G::Usize) -> *mut u8 { |
508 | 5.41k | let (base, _) = self.mem.as_raw_parts(); |
509 | 5.41k | base.wrapping_add(G::usize_to_host(addr) + BEGIN_GUARD_SIZE) |
510 | 5.41k | } |
511 | | |
512 | | /// Get a read-only slice of memory by WASM address. |
513 | 2.05k | pub fn slice_by_wasm_addr(&self, offset: G::Usize, length: G::Usize) -> &[u8] { |
514 | 2.05k | unsafe { std::slice::from_raw_parts(self.host_ptr_by_wasm_addr(offset), G::usize_to_host(length)) } |
515 | 2.05k | } |
516 | | |
517 | | /// Get a mutable slice of the memory with WASM address. |
518 | | /// |
519 | | /// # Safety |
520 | | /// |
521 | | /// The caller must ensure no other live references alias the returned range for the duration |
522 | | /// of the returned slice. |
523 | | #[allow(clippy::mut_from_ref)] |
524 | 3.36k | pub unsafe fn slice_by_wasm_addr_mut(&self, offset: G::Usize, length: G::Usize) -> &mut [u8] { |
525 | 3.36k | unsafe { std::slice::from_raw_parts_mut(self.host_ptr_by_wasm_addr(offset), G::usize_to_host(length)) } |
526 | 3.36k | } |
527 | | |
528 | | /// Get the function category of this LyteMemory's owner. |
529 | 370 | pub fn category(&self) -> StateCategory { |
530 | 370 | self.category |
531 | 370 | } |
532 | | |
533 | | /// Return the wait/notify queue associated with the shared instance segment. |
534 | 140 | pub(crate) fn instance_parking_spot(&self) -> Arc<crate::sync::ParkingSpot> { |
535 | 140 | self.ctx.instance.parking.clone() |
536 | 140 | } |
537 | | } |
538 | | |
539 | | struct WasmRuntimeMemory<G: VmGuest> { |
540 | | inner: LyteMemory<G>, |
541 | | size: usize, |
542 | | } |
543 | | |
544 | | unsafe impl<G: VmGuest> wasmtime::LinearMemory for WasmRuntimeMemory<G> { |
545 | 140 | fn byte_size(&self) -> usize { |
546 | 140 | self.size |
547 | 140 | } |
548 | | |
549 | 0 | fn byte_capacity(&self) -> usize { |
550 | 0 | self.size |
551 | 0 | } |
552 | | |
553 | 0 | fn grow_to(&mut self, size: usize) -> wasmtime::Result<()> { |
554 | 0 | if size > self.size { |
555 | 0 | return Err(wasmtime::Error::msg("LyteMemory is not growable.")); |
556 | 0 | } |
557 | 0 | Ok(()) |
558 | 0 | } |
559 | | |
560 | 70 | fn as_ptr(&self) -> *mut u8 { |
561 | 70 | let (ptr, _) = self.inner.mem.as_raw_parts(); |
562 | 70 | unsafe { ptr.add(BEGIN_GUARD_SIZE) } |
563 | 70 | } |
564 | | } |
565 | | |
566 | | /// Memory factory that makes wasmtime memory for the given LyteMemory. |
567 | | pub struct WasmMemoryCreator<G: VmGuest = Wasm32>(pub LyteMemory<G>); |
568 | | |
569 | | unsafe impl<G: VmGuest> wasmtime::MemoryCreator for WasmMemoryCreator<G> { |
570 | 70 | fn new_memory( |
571 | 70 | &self, _mt: wasmtime::MemoryType, _min: usize, _max: Option<usize>, _reserved: Option<usize>, _guard: usize, |
572 | 70 | ) -> Result<Box<dyn wasmtime::LinearMemory>, String> { |
573 | 70 | Ok(Box::new(WasmRuntimeMemory { |
574 | 70 | inner: self.0.clone(), |
575 | 70 | size: lyquid::LYTEMEM_SIZE_IN_MB << 20, |
576 | 70 | })) |
577 | 70 | } |
578 | | } |