microvms_core/sizing.rs
1// SPDX-License-Identifier: Apache-2.0
2//! Size classes: what `minimumMemoryInMiB` actually buys, and what it bills.
3//!
4//! `minimumMemoryInMiB` does not size a VM. It selects a *class*, and the class has
5//! two numbers that differ by 4x — the baseline you are billed for every running
6//! second, and the peak the guest reports in `/proc/meminfo`. Measured 2026-08-07,
7//! us-east-1: requesting 512 produced a guest reporting ~2 GB `MemTotal`; requesting
8//! 2048 produced ~8 GB. Both match AWS's documented sizing table exactly, so the
9//! number the caller writes down is neither the number they get nor, on its own,
10//! enough to predict the bill (`docs/PLATFORM.md`, "`minimumMemoryInMiB` selects a
11//! *baseline*, and the guest reports the *peak*").
12//!
13//! The peak is provisioned from the start (service team, confirmed 2026-08): there
14//! is no scaling event, nothing resizes during a run, and app code never observes a
15//! resource change. The baseline is the bill's floor — always paid while the VM
16//! runs — and usage above it is billed by consumption.
17//!
18//! # Why the table is data (TRAP-13)
19//!
20//! Every documented peak is exactly four times its baseline, which makes
21//! `baseline * 4` look like the obvious simplification. It is the one thing this
22//! module must not do. That regularity is AWS's and not ours: a sixth row, or a
23//! revision to an existing one, that breaks the pattern would silently get the
24//! pattern applied to it, and the report would name a provisioned ceiling the service
25//! does not offer. So [`SIZE_CLASSES`] is the only place any of these numbers appears, and
26//! every accessor reads a row out of it through one lookup — which is also what makes
27//! the guard testable: the test below drives the accessors over a table whose peak is
28//! *not* 4x, and a computed implementation cannot answer it.
29//!
30//! # Why an off-table request is refused (TRAP-10)
31//!
32//! Only the five table values are accepted. What the service does with an off-table
33//! request such as 1500 is undocumented and unmeasured, and the two plausible
34//! behaviours — round up to the enclosing class, or take the literal as a baseline —
35//! differ in both the memory the guest gets and the rate it is billed at. Rejecting
36//! locally costs the caller a second; guessing costs them a wrong bill they have no
37//! way to notice.
38
39use std::fmt;
40
41use crate::error::Error;
42
43/// MiB per GB as AWS's pricing and sizing tables use it.
44///
45/// The guest's `MemTotal` reads in KiB and lands slightly under (2 GB shows as
46/// 2037648 kB), so any comparison against a guest-reported figure is approximate by
47/// nature.
48pub const MIB_PER_GB: u32 = 1024;
49
50/// One row of the documented sizing table: a baseline and its provisioned ceiling.
51///
52/// Plain data with public fields, because that is what it is — a transcription of
53/// someone else's published table, not settings. The accessors on [`SizeClass`] are
54/// where a caller normally reads it; the row is public so the drift gate and the
55/// guard test can compare against the table itself.
56#[derive(Clone, Copy, Debug, PartialEq)]
57pub struct SizeRow {
58 /// What `minimumMemoryInMiB` must be set to in order to select this class.
59 pub baseline_mib: u32,
60 /// Billed per running second, alongside `baseline_vcpu`. Not what the guest
61 /// reports.
62 pub baseline_vcpu: f64,
63 /// What the guest reports as `MemTotal`: a fixed ceiling, provisioned from the
64 /// start and present the whole run. Usage above the baseline is billed by
65 /// consumption, per second.
66 pub peak_mib: u32,
67 pub peak_vcpu: f64,
68}
69
70/// The documented table (`microvms-images.html`), smallest first.
71///
72/// The single definition of all twenty numbers. See the module docs for why nothing
73/// derives one of them from another.
74pub const SIZE_CLASSES: [SizeRow; 5] = [
75 SizeRow {
76 baseline_mib: 512,
77 baseline_vcpu: 0.25,
78 peak_mib: 2048,
79 peak_vcpu: 1.0,
80 },
81 SizeRow {
82 baseline_mib: 1024,
83 baseline_vcpu: 0.5,
84 peak_mib: 4096,
85 peak_vcpu: 2.0,
86 },
87 SizeRow {
88 baseline_mib: 2048,
89 baseline_vcpu: 1.0,
90 peak_mib: 8192,
91 peak_vcpu: 4.0,
92 },
93 SizeRow {
94 baseline_mib: 4096,
95 baseline_vcpu: 2.0,
96 peak_mib: 16384,
97 peak_vcpu: 8.0,
98 },
99 SizeRow {
100 baseline_mib: 8192,
101 baseline_vcpu: 4.0,
102 peak_mib: 32768,
103 peak_vcpu: 16.0,
104 },
105];
106
107/// One of the five documented size classes.
108///
109/// Closed, so an off-table memory figure is not a value that exists — the Python
110/// client's S2 runtime check (`sizing.py:95` `size_class_for`) becomes S1 for every
111/// caller who has a `SizeClass` in hand. [`SizeClass::from_baseline_mib`] is the one
112/// boundary where a bare integer still has to be judged, which is where TRAP-10
113/// lives.
114///
115/// The variants are named for their baseline because that is the number the caller
116/// writes into `minimumMemoryInMiB`; the peak is deliberately not in any name, since
117/// naming both would suggest a caller gets to pick them independently.
118#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
119pub enum SizeClass {
120 Mib512,
121 Mib1024,
122 Mib2048,
123 Mib4096,
124 Mib8192,
125}
126
127impl SizeClass {
128 /// The platform's own default, and ours.
129 ///
130 /// Deliberately not the smallest class, and the reason is the CEILING: picking
131 /// 512 gives the guest a hard 2 GiB total — provisioned from the start, fixed
132 /// for the whole run, nothing resizes past it — and a real test suite breaches
133 /// it. Guest swap is absent (`SwapTotal: 0 kB`), so there is no paging phase to
134 /// absorb the mistake — pressure goes straight to the OOM killer. The default's
135 /// job is a ceiling that survives a real test suite, and 8 GiB does.
136 pub const DEFAULT: SizeClass = SizeClass::Mib2048;
137
138 /// Every class, smallest first, in [`SIZE_CLASSES`] order.
139 pub const ALL: [SizeClass; 5] = [
140 SizeClass::Mib512,
141 SizeClass::Mib1024,
142 SizeClass::Mib2048,
143 SizeClass::Mib4096,
144 SizeClass::Mib8192,
145 ];
146
147 /// The class `minimumMemoryInMiB = mib` selects (TRAP-10).
148 ///
149 /// Rejects anything not in the table rather than snapping to a neighbour: an
150 /// off-table request has two plausible readings that differ in both memory and
151 /// rate, and we have measured neither. This is the boundary a figure from a CLI
152 /// flag or a config file crosses.
153 pub fn from_baseline_mib(mib: u32) -> Result<SizeClass, Error> {
154 if let Some(class) = class_for_baseline_in(&SIZE_CLASSES, mib) {
155 return Ok(class);
156 }
157 let offered = SIZE_CLASSES
158 .iter()
159 .map(|row| row.baseline_mib.to_string())
160 .collect::<Vec<_>>()
161 .join(", ");
162 Err(Error::invalid_arg(format!(
163 "minimumMemoryInMiB={mib} is not a documented size class baseline. The field \
164 selects a class, it does not size a VM; pass one of {offered} MiB. See \
165 docs/PLATFORM.md, '`minimumMemoryInMiB` selects a *baseline*, and the guest \
166 reports the *peak*'."
167 )))
168 }
169
170 /// This class's row of the documented table.
171 ///
172 /// The one reader of [`SIZE_CLASSES`] for a known class, which is what keeps
173 /// every accessor below reading data rather than deriving it.
174 pub fn row(self) -> &'static SizeRow {
175 row_in(&SIZE_CLASSES, self)
176 }
177
178 /// The value to send as `minimumMemoryInMiB`. Billed per running second.
179 pub fn baseline_mib(self) -> u32 {
180 self.row().baseline_mib
181 }
182
183 /// What the guest reports as `MemTotal`: the provisioned ceiling, present from
184 /// the start of the run.
185 ///
186 /// Read from the table. Never `baseline_mib() * 4` — see the module docs.
187 pub fn peak_mib(self) -> u32 {
188 self.row().peak_mib
189 }
190
191 /// The static headroom above the baseline: peak minus baseline, both read from
192 /// this class's table row.
193 ///
194 /// Fixed at provision time — the headroom is always present, never granted by a
195 /// scaling event — and usage inside it is billed by consumption. Computed as a
196 /// difference of two *table* values, never as `baseline * 3`: the 4x regularity
197 /// is AWS's, not ours, and a row that breaks it must be reported as written
198 /// (TRAP-13, same discipline as [`SizeClass::peak_mib`]).
199 pub fn headroom_mib(self) -> u32 {
200 headroom_in(&SIZE_CLASSES, self)
201 }
202
203 /// Billed per running second, alongside [`SizeClass::baseline_mib`].
204 pub fn baseline_vcpu(self) -> f64 {
205 self.row().baseline_vcpu
206 }
207
208 /// The provisioned vCPU ceiling, present from the start. Read from the table.
209 pub fn peak_vcpu(self) -> f64 {
210 self.row().peak_vcpu
211 }
212
213 /// The figure a GB-second rate multiplies. Always the baseline, never the peak
214 /// (COST-5).
215 pub fn baseline_gb(self) -> f64 {
216 f64::from(self.baseline_mib()) / f64::from(MIB_PER_GB)
217 }
218
219 /// The peak in GB, for a report that shows the caller what the guest will say.
220 pub fn peak_gb(self) -> f64 {
221 f64::from(self.peak_mib()) / f64::from(MIB_PER_GB)
222 }
223
224 /// The position of this class in [`SIZE_CLASSES`].
225 ///
226 /// Private and `const`: it is the identity mapping between the enum and the
227 /// table, and the invariant test below pins it to the baselines so a reordered
228 /// table cannot quietly re-point every variant.
229 const fn index(self) -> usize {
230 match self {
231 SizeClass::Mib512 => 0,
232 SizeClass::Mib1024 => 1,
233 SizeClass::Mib2048 => 2,
234 SizeClass::Mib4096 => 3,
235 SizeClass::Mib8192 => 4,
236 }
237 }
238}
239
240/// One line naming both numbers, for an error message or a CLI.
241///
242/// Both numbers or neither: naming only the peak invites someone to budget for
243/// memory they will not be billed for, and naming only the baseline invites a
244/// memory-pressure test written against a ceiling four times lower than the one the
245/// guest enforces.
246impl fmt::Display for SizeClass {
247 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248 let row = self.row();
249 write!(
250 f,
251 "{} GB / {} vCPU baseline (billed while running), with a fixed ceiling of {} GB / \
252 {} vCPU provisioned from the start (what the guest reports)",
253 self.baseline_gb(),
254 row.baseline_vcpu,
255 self.peak_gb(),
256 row.peak_vcpu,
257 )
258 }
259}
260
261/// The row a class occupies in `table`.
262///
263/// Takes the table as a parameter for one reason: it is what makes TRAP-13's guard
264/// falsifiable. Every accessor above goes through here, so a test can hand this
265/// function a table whose peak is not four times its baseline and see whether the
266/// answer follows the data or the pattern.
267fn row_in(table: &[SizeRow], class: SizeClass) -> &SizeRow {
268 &table[class.index()]
269}
270
271/// The headroom `table`'s row carries for `class`: its peak minus its baseline.
272///
273/// Table-parameterized for the same reason as [`row_in`]: a test can hand this a
274/// table whose peak is not four times its baseline and see whether the headroom
275/// follows the data or the pattern. Saturating rather than plain subtraction so a
276/// hostile test table whose peak is below its baseline reads as zero headroom
277/// rather than a panic.
278fn headroom_in(table: &[SizeRow], class: SizeClass) -> u32 {
279 let row = row_in(table, class);
280 row.peak_mib.saturating_sub(row.baseline_mib)
281}
282
283/// The class whose row in `table` carries `baseline_mib`, if any.
284///
285/// Table-parameterized for the same reason as [`row_in`], and a linear scan because
286/// five rows is five rows.
287fn class_for_baseline_in(table: &[SizeRow], baseline_mib: u32) -> Option<SizeClass> {
288 SizeClass::ALL
289 .into_iter()
290 .find(|class| table[class.index()].baseline_mib == baseline_mib)
291}
292
293#[cfg(test)]
294mod tests {
295 use proptest::prelude::*;
296
297 use super::*;
298 use crate::error::ErrorKind;
299
300 /// The measured table, written out as literals. This is the test a table edit
301 /// must fail: the twenty numbers are transcriptions of AWS's published table and
302 /// two of the rows were measured directly, so changing one is a claim about the
303 /// service that needs its own evidence.
304 #[test]
305 fn the_documented_table_carries_the_measured_rows() {
306 let rows: Vec<(u32, f64, u32, f64)> = SIZE_CLASSES
307 .iter()
308 .map(|r| (r.baseline_mib, r.baseline_vcpu, r.peak_mib, r.peak_vcpu))
309 .collect();
310 assert_eq!(
311 rows,
312 [
313 (512, 0.25, 2048, 1.0),
314 (1024, 0.5, 4096, 2.0),
315 (2048, 1.0, 8192, 4.0),
316 (4096, 2.0, 16384, 8.0),
317 (8192, 4.0, 32768, 16.0),
318 ]
319 );
320 }
321
322 /// TRAP-13. The lookup answers from the data, so a table whose peak breaks the
323 /// 4x pattern is reported as written rather than as the pattern predicts.
324 ///
325 /// This is the falsification the spec asks for: replace [`SizeClass::peak_mib`]'s
326 /// body with `self.baseline_mib() * 4` and this row's peak comes back 8192
327 /// instead of 9000. A test that only checked the shipped table could not tell the
328 /// two implementations apart, because every shipped peak *is* 4x its baseline.
329 #[test]
330 fn a_peak_that_is_not_four_times_its_baseline_is_read_not_computed() {
331 let mut table = SIZE_CLASSES;
332 table[SizeClass::Mib2048.index()].peak_mib = 9000;
333 table[SizeClass::Mib2048.index()].peak_vcpu = 3.5;
334
335 let row = row_in(&table, SizeClass::Mib2048);
336 assert_eq!(
337 row.peak_mib, 9000,
338 "the peak must come from the table, not from baseline * 4"
339 );
340 assert_eq!(row.peak_vcpu, 3.5);
341 // The baseline is untouched by the edit, which is what makes the assertion
342 // above about the peak rather than about the row being found at all.
343 assert_eq!(row.baseline_mib, 2048);
344 assert_eq!(row.peak_mib * 4 / 4, 9000);
345 assert_ne!(row.peak_mib, row.baseline_mib * 4);
346 }
347
348 /// The public accessors are that lookup applied to the shipped table, and
349 /// nothing else. Under a computed implementation this passes today and goes red
350 /// the moment a table row stops being 4x — which is exactly the regression
351 /// TRAP-13 names.
352 #[test]
353 fn every_accessor_returns_its_own_row_from_the_shipped_table() {
354 for class in SizeClass::ALL {
355 let row = &SIZE_CLASSES[class.index()];
356 assert_eq!(class.baseline_mib(), row.baseline_mib, "{class:?}");
357 assert_eq!(class.peak_mib(), row.peak_mib, "{class:?}");
358 assert_eq!(class.baseline_vcpu(), row.baseline_vcpu, "{class:?}");
359 assert_eq!(class.peak_vcpu(), row.peak_vcpu, "{class:?}");
360 }
361 }
362
363 /// The enum-to-row mapping is pinned to the baselines rather than to positions,
364 /// so a reordered table fails here instead of quietly re-pointing every variant
365 /// at its neighbour's numbers.
366 #[test]
367 fn each_variant_is_pinned_to_the_baseline_its_name_states() {
368 assert_eq!(SizeClass::Mib512.baseline_mib(), 512);
369 assert_eq!(SizeClass::Mib1024.baseline_mib(), 1024);
370 assert_eq!(SizeClass::Mib2048.baseline_mib(), 2048);
371 assert_eq!(SizeClass::Mib4096.baseline_mib(), 4096);
372 assert_eq!(SizeClass::Mib8192.baseline_mib(), 8192);
373 }
374
375 /// TRAP-10: the five documented baselines are accepted and answer with their own
376 /// class.
377 #[test]
378 fn the_five_documented_baselines_select_their_own_class() {
379 for class in SizeClass::ALL {
380 let selected = SizeClass::from_baseline_mib(class.baseline_mib())
381 .expect("a documented baseline is accepted");
382 assert_eq!(selected, class);
383 }
384 }
385
386 /// TRAP-10's falsification, at the values a caller actually reaches for: a round
387 /// number between two classes, the peak of a class mistaken for a request, and
388 /// zero. 1500 is the case in `sizing.py`'s own docstring — the reading is
389 /// ambiguous in a way that changes the bill, so it is refused rather than
390 /// snapped.
391 #[test]
392 fn an_off_table_baseline_is_refused_naming_the_finding() {
393 for mib in [0, 1, 511, 513, 1500, 3072, 8191, 16384, u32::MAX] {
394 let err = SizeClass::from_baseline_mib(mib)
395 .expect_err("only the five documented baselines are accepted");
396 assert_eq!(err.kind(), ErrorKind::InvalidArg, "{mib}");
397 let message = err.to_string();
398 assert!(
399 message.contains("not a documented size class baseline"),
400 "{message}"
401 );
402 assert!(
403 message.contains("selects a class, it does not size a VM"),
404 "must name the finding: {message}"
405 );
406 }
407 }
408
409 /// A peak is not a baseline. 2048 is both — the peak of the smallest class and
410 /// the baseline of the default — and 8192 is both the peak of the default and the
411 /// largest baseline, so a caller who read a `MemTotal` and passed it back gets a
412 /// silently different class rather than an error. Nothing can be done about that
413 /// (both are legal requests), which is why `Display` always names both numbers.
414 /// 16384 and 32768 are peaks that are *not* baselines and must be refused.
415 #[test]
416 fn a_peak_that_is_not_also_a_baseline_is_refused() {
417 assert_eq!(
418 SizeClass::from_baseline_mib(2048).expect("2048 is a baseline too"),
419 SizeClass::Mib2048
420 );
421 for peak_only in [16384, 32768] {
422 assert!(
423 SizeClass::from_baseline_mib(peak_only).is_err(),
424 "{peak_only}"
425 );
426 }
427 }
428
429 /// The default is the platform's, not the cheapest, and the reason is the
430 /// ceiling: a default of `Mib512` gives the guest a hard 2 GB total —
431 /// provisioned from the start, fixed for the whole run — which a real test
432 /// suite breaches, and there is no swap to absorb the breach. The default's
433 /// job is a ceiling that survives real work.
434 #[test]
435 fn the_default_class_is_two_gigabytes_of_baseline() {
436 assert_eq!(SizeClass::DEFAULT, SizeClass::Mib2048);
437 assert_eq!(SizeClass::DEFAULT.baseline_mib(), 2048);
438 assert_eq!(SizeClass::DEFAULT.baseline_gb(), 2.0);
439 assert_eq!(SizeClass::DEFAULT.peak_gb(), 8.0);
440 }
441
442 /// Both numbers or neither, so nobody budgets for memory they are not billed for
443 /// and nobody writes a pressure test against the wrong ceiling.
444 #[test]
445 fn display_names_the_baseline_and_the_peak_together() {
446 let described = SizeClass::DEFAULT.to_string();
447 assert!(described.contains("2 GB / 1 vCPU baseline"), "{described}");
448 assert!(described.contains("8 GB / 4 vCPU"), "{described}");
449 assert!(described.contains("billed while running"), "{described}");
450 assert!(described.contains("what the guest reports"), "{described}");
451 // The mechanism, stated as it is: a fixed ceiling present from the start,
452 // not a "burst" the service would have to grant.
453 assert!(
454 described.contains("provisioned from the start"),
455 "{described}"
456 );
457 assert!(!described.contains("burst"), "{described}");
458 }
459
460 /// TRAP-13 for the headroom: peak minus baseline, both read from the table.
461 ///
462 /// The falsification: replace [`SizeClass::headroom_mib`]'s body with
463 /// `self.baseline_mib() * 3` — arithmetically identical over the shipped table,
464 /// where every peak is 4x — and this row's headroom comes back 6144 instead of
465 /// 6952. A test over the shipped table alone could not tell the two apart.
466 #[test]
467 fn headroom_follows_a_table_whose_peak_is_not_four_times_its_baseline() {
468 let mut table = SIZE_CLASSES;
469 table[SizeClass::Mib2048.index()].peak_mib = 9000;
470 assert_eq!(
471 headroom_in(&table, SizeClass::Mib2048),
472 9000 - 2048,
473 "headroom must be a difference of table values, not baseline * 3"
474 );
475 // The shipped table's headroom for every class, through the public accessor.
476 for class in SizeClass::ALL {
477 let row = &SIZE_CLASSES[class.index()];
478 assert_eq!(
479 class.headroom_mib(),
480 row.peak_mib - row.baseline_mib,
481 "{class:?}"
482 );
483 }
484 }
485
486 /// The band where an off-table figure is *plausible*, sampled deliberately.
487 ///
488 /// A uniform `u32` draw is the wrong generator for TRAP-10 and measuring it
489 /// proved so: with the whole domain to pick from, essentially every case lands
490 /// above 8192, where the only reachable verdict is "refused" and a
491 /// round-up-to-the-enclosing-class implementation is indistinguishable from a
492 /// refusing one. Snapping can only happen *between* the smallest and largest
493 /// baseline, so the property has to be told to look there. The band is what
494 /// a caller actually types.
495 fn plausible_baseline_mib() -> impl Strategy<Value = u32> {
496 prop_oneof![
497 // The whole domain, so nothing outside the band goes unchecked.
498 2 => any::<u32>(),
499 // The band where an enclosing class exists to snap to.
500 5 => 1u32..=8192,
501 // The neighbourhoods of each documented baseline, where an off-by-one or
502 // a nearest-neighbour rule would hide.
503 3 => prop::sample::select(SIZE_CLASSES.to_vec())
504 .prop_flat_map(|row| (row.baseline_mib.saturating_sub(2))..=(row.baseline_mib + 2)),
505 ]
506 }
507
508 proptest! {
509 /// The verdict, over the whole `u32` domain plus the band where an off-table
510 /// figure is plausible: accepted exactly when the figure is one of the five,
511 /// refused as `ERR_INVALID_ARG` otherwise. Asserting the verdict rather than a
512 /// side effect is the lesson from
513 /// `.erpaval/solutions/test-failures/proptest-and-dst-tiers-need-verdict-assertions.md`
514 /// — a property that only checked "no panic" would pass against a function
515 /// that accepted everything.
516 ///
517 /// The `Ok` arm's second assertion is the one that catches snapping: a
518 /// rounding implementation returns a class whose baseline is *not* the figure
519 /// it was handed, so `class.baseline_mib() == mib` is what says the answer is
520 /// the caller's request rather than a nearby one chosen for them.
521 #[test]
522 fn from_baseline_mib_accepts_exactly_the_five_documented_baselines(
523 mib in plausible_baseline_mib(),
524 ) {
525 let documented = SIZE_CLASSES.iter().any(|row| row.baseline_mib == mib);
526 match SizeClass::from_baseline_mib(mib) {
527 Ok(class) => {
528 prop_assert!(documented, "{mib} MiB is not a documented baseline but was accepted");
529 prop_assert_eq!(class.baseline_mib(), mib);
530 }
531 Err(err) => {
532 prop_assert!(!documented, "{} MiB is documented but was refused", mib);
533 prop_assert_eq!(err.kind(), ErrorKind::InvalidArg);
534 prop_assert_eq!(err.code(), "ERR_INVALID_ARG");
535 }
536 }
537 }
538
539 /// Whatever a table says, the lookup says. Generated peaks are deliberately
540 /// unrelated to their baselines, so a computed implementation disagrees with
541 /// the table on almost every input.
542 #[test]
543 fn the_lookup_reports_whatever_peak_the_table_carries(
544 peak_mib in 1u32..100_000,
545 peak_vcpu in 0.1f64..64.0,
546 ) {
547 let mut table = SIZE_CLASSES;
548 for class in SizeClass::ALL {
549 table[class.index()].peak_mib = peak_mib;
550 table[class.index()].peak_vcpu = peak_vcpu;
551 }
552 for class in SizeClass::ALL {
553 let row = row_in(&table, class);
554 prop_assert_eq!(row.peak_mib, peak_mib);
555 prop_assert_eq!(row.peak_vcpu, peak_vcpu);
556 // The baselines are untouched, so the row is still the right row.
557 prop_assert_eq!(row.baseline_mib, class.baseline_mib());
558 prop_assert_eq!(
559 class_for_baseline_in(&table, class.baseline_mib()),
560 Some(class)
561 );
562 }
563 }
564 }
565}