Coverage Report

Created: 2026-06-13 00:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}