Coverage Report

Created: 2026-10-25 01:51

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::{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
}