Skip to main content

rucc_ir/
flags.rs

1//! Instruction flags, atomic orderings, and the read-modify-write operations.
2//!
3//! Design: `spec/08-ir.md` section 8.4.
4//!
5//! Nearly every flag is a licence the frontend grants the optimizer, and each of those is tied to
6//! something the C standard leaves undefined. `-fwrapv` is implemented by not setting
7//! [`Flags::NSW`], and that is the whole of it.
8//!
9//! [`Flags::NOFREE`] is the one that is not a licence. It is a fact about what a call reaches,
10//! worked out over the whole module, and it is written onto the call site because a pass is given
11//! one function and the fact belongs to a different one. The frontend does the same thing with a
12//! call that never comes back: it puts an `unreachable` after it rather than expecting every later
13//! pass to go and look the callee up.
14//!
15//! **There is no poison.** An `add nsw` that overflows does not produce a value that taints
16//! everything downstream. It produces an unspecified but stable value, meaning two reads of it
17//! agree, and `nsw` licenses only the specific rewrites the rule set proves sound under the
18//! assumption that the overflow does not happen. The cost is real, and it is that arithmetic
19//! cannot be speculated across control flow as aggressively. The benefit is that every rewrite
20//! is locally justifiable, which is what keeps the rule set verifiable, and that a wrong answer
21//! cannot travel from somewhere the user cannot see to somewhere they can.
22//!
23//! The fast-math flags sit on individual instructions rather than in a global mode, so
24//! `-ffast-math` is a decision the frontend makes per expression. That is what keeps link time
25//! optimization across a unit built with it and a unit built without it correct.
26
27use std::fmt;
28
29use crate::Opcode;
30
31/// The flags on one instruction.
32///
33/// A bitset rather than a struct of `bool`s, because it rides along in the instruction table
34/// and two bytes there is two bytes per instruction in every function in the program.
35#[derive(Clone, Copy, PartialEq, Eq, Default, Hash)]
36pub struct Flags(u16);
37
38impl Flags {
39    /// No flags, which is what `-O0` and `-fwrapv` and a plain unsigned addition all produce.
40    pub const NONE: Self = Self(0);
41
42    /// No signed wrap. Signed overflow is undefined, so the optimizer may assume it does not
43    /// happen. `-fwrapv` stops the frontend setting this and nothing else changes.
44    pub const NSW: Self = Self(1 << 0);
45    /// No unsigned wrap. Set only where the frontend knows it from the source, since C's
46    /// unsigned arithmetic wraps by definition and most unsigned arithmetic does not get this.
47    pub const NUW: Self = Self(1 << 1);
48    /// The shift or division is exact, so no bits are discarded and no remainder is dropped.
49    pub const EXACT: Self = Self(1 << 2);
50
51    /// No NaN operands or results.
52    pub const NNAN: Self = Self(1 << 3);
53    /// No infinite operands or results.
54    pub const NINF: Self = Self(1 << 4);
55    /// The sign of a zero does not matter.
56    pub const NSZ: Self = Self(1 << 5);
57    /// A division may become a multiplication by the reciprocal.
58    pub const ARCP: Self = Self(1 << 6);
59    /// A multiplication and an addition may be contracted into one rounding.
60    pub const CONTRACT: Self = Self(1 << 7);
61    /// The operation may be reassociated, which is the one that changes results the most.
62    pub const REASSOC: Self = Self(1 << 8);
63
64    /// The access is `volatile`, so it happens exactly once and is never moved or merged.
65    pub const VOLATILE: Self = Self(1 << 9);
66    /// The result does not alias anything else reachable, which is what `restrict` gives.
67    pub const NOALIAS: Self = Self(1 << 10);
68
69    /// Nothing this call reaches ends the lifetime of any storage.
70    ///
71    /// The `nofree` summary of `spec/safe-memory/07-check-elimination.md` section 7.5, written onto
72    /// the call site by a module-level analysis rather than by the frontend. A pass carrying what
73    /// an earlier safety check established keeps it across a call that has this and gives it up
74    /// across a call that does not.
75    pub const NOFREE: Self = Self(1 << 11);
76
77    /// Every fast-math flag, which is what `-ffast-math` sets on an expression.
78    pub const FAST: Self = Self(
79        Self::NNAN.0
80            | Self::NINF.0
81            | Self::NSZ.0
82            | Self::ARCP.0
83            | Self::CONTRACT.0
84            | Self::REASSOC.0,
85    );
86
87    /// The underlying bits, for the printer and for hashing an instruction.
88    #[must_use]
89    pub const fn bits(self) -> u16 {
90        self.0
91    }
92
93    /// Whether nothing is set.
94    #[must_use]
95    pub const fn is_empty(self) -> bool {
96        self.0 == 0
97    }
98
99    /// Whether every flag in `other` is set here.
100    #[must_use]
101    pub const fn contains(self, other: Self) -> bool {
102        self.0 & other.0 == other.0
103    }
104
105    /// Both sets.
106    #[must_use]
107    pub const fn union(self, other: Self) -> Self {
108        Self(self.0 | other.0)
109    }
110
111    /// The flags in both sets.
112    ///
113    /// This is what a rewrite does when it replaces two instructions with one: a licence
114    /// granted on one of them and not the other is not a licence over the result.
115    #[must_use]
116    pub const fn intersection(self, other: Self) -> Self {
117        Self(self.0 & other.0)
118    }
119
120    /// This set without the flags in `other`.
121    #[must_use]
122    pub const fn without(self, other: Self) -> Self {
123        Self(self.0 & !other.0)
124    }
125
126    /// The flags that mean anything on that opcode.
127    ///
128    /// Anything outside this is a verifier failure rather than something ignored, because a
129    /// flag on an instruction that does not read it is a flag somebody meant to put somewhere
130    /// else.
131    #[must_use]
132    pub const fn legal_on(opcode: Opcode) -> Self {
133        match opcode {
134            Opcode::Add | Opcode::Sub | Opcode::Mul | Opcode::Shl => Self::NSW.union(Self::NUW),
135            Opcode::SDiv | Opcode::UDiv | Opcode::LShr | Opcode::AShr => Self::EXACT,
136            Opcode::FAdd
137            | Opcode::FSub
138            | Opcode::FMul
139            | Opcode::FDiv
140            | Opcode::FRem
141            | Opcode::FNeg
142            | Opcode::Fma
143            | Opcode::FCmp => Self::FAST,
144            Opcode::Load | Opcode::Store | Opcode::Memcpy | Opcode::Memmove | Opcode::Memset => {
145                Self::VOLATILE
146            }
147            Opcode::InlineAsm => Self::VOLATILE,
148            // On all three spellings of a call, including the indirect one. Nothing works out
149            // `nofree` for a call through an address today, and the flag is legal there because
150            // what it says is about the functions the call reaches rather than about how the call
151            // names them, so a later analysis that knows the targets has somewhere to write it.
152            Opcode::Call | Opcode::TailCall | Opcode::CallIndirect => Self::NOFREE,
153            Opcode::Alloca | Opcode::PtrAdd => Self::NOALIAS,
154            _ => Self::NONE,
155        }
156    }
157
158    /// Every flag that is set, with its name, in the order the printer writes them.
159    pub fn iter(self) -> impl Iterator<Item = (Self, &'static str)> {
160        NAMED.iter().copied().filter(move |&(flag, _)| self.contains(flag))
161    }
162
163    /// The flag with that name, if there is one.
164    #[must_use]
165    pub fn from_name(name: &str) -> Option<Self> {
166        NAMED.iter().find(|&&(_, named)| named == name).map(|&(flag, _)| flag)
167    }
168}
169
170impl std::ops::BitOr for Flags {
171    type Output = Self;
172
173    fn bitor(self, other: Self) -> Self {
174        self.union(other)
175    }
176}
177
178impl std::ops::BitOrAssign for Flags {
179    fn bitor_assign(&mut self, other: Self) {
180        *self = self.union(other);
181    }
182}
183
184impl fmt::Display for Flags {
185    /// The suffix form the textual IR uses, `add.nsw`, with a leading dot on each flag and
186    /// nothing at all when the set is empty.
187    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188        for (_, name) in self.iter() {
189            write!(f, ".{name}")?;
190        }
191        Ok(())
192    }
193}
194
195impl fmt::Debug for Flags {
196    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
197        if self.is_empty() {
198            return f.write_str("Flags::NONE");
199        }
200        fmt::Display::fmt(self, f)
201    }
202}
203
204/// Each flag with its name, in printing order.
205static NAMED: &[(Flags, &str)] = &[
206    (Flags::NSW, "nsw"),
207    (Flags::NUW, "nuw"),
208    (Flags::EXACT, "exact"),
209    (Flags::NNAN, "nnan"),
210    (Flags::NINF, "ninf"),
211    (Flags::NSZ, "nsz"),
212    (Flags::ARCP, "arcp"),
213    (Flags::CONTRACT, "contract"),
214    (Flags::REASSOC, "reassoc"),
215    (Flags::VOLATILE, "volatile"),
216    (Flags::NOALIAS, "noalias"),
217    (Flags::NOFREE, "nofree"),
218];
219
220/// How strongly an atomic operation is ordered against everything around it.
221///
222/// These are C11's, minus `consume`, which every compiler in existence widens to `acquire`
223/// because nobody can implement it as specified and the standard committee has said so.
224#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
225pub enum MemOrder {
226    /// Not atomic at all, which is what an ordinary load or store is.
227    #[default]
228    NotAtomic,
229    /// Atomic, with no ordering against anything else.
230    Relaxed,
231    /// Nothing after this in program order moves before it.
232    Acquire,
233    /// Nothing before this in program order moves after it.
234    Release,
235    /// Both, for a read-modify-write.
236    AcqRel,
237    /// Both, and a single total order over every sequentially consistent operation.
238    SeqCst,
239}
240
241impl MemOrder {
242    /// The textual form.
243    #[must_use]
244    pub const fn name(self) -> &'static str {
245        match self {
246            Self::NotAtomic => "not_atomic",
247            Self::Relaxed => "relaxed",
248            Self::Acquire => "acquire",
249            Self::Release => "release",
250            Self::AcqRel => "acq_rel",
251            Self::SeqCst => "seq_cst",
252        }
253    }
254
255    /// The ordering with that name, if there is one.
256    #[must_use]
257    pub fn from_name(name: &str) -> Option<Self> {
258        Self::all().find(|order| order.name() == name)
259    }
260
261    /// Every ordering, weakest first.
262    pub fn all() -> impl Iterator<Item = Self> {
263        [Self::NotAtomic, Self::Relaxed, Self::Acquire, Self::Release, Self::AcqRel, Self::SeqCst]
264            .into_iter()
265    }
266
267    /// Whether this ordering can be asked of a load.
268    ///
269    /// A load cannot release, because there is nothing it published.
270    #[must_use]
271    pub const fn is_valid_for_load(self) -> bool {
272        matches!(self, Self::Relaxed | Self::Acquire | Self::SeqCst)
273    }
274
275    /// Whether this ordering can be asked of a store.
276    ///
277    /// A store cannot acquire, because it read nothing to synchronise with.
278    #[must_use]
279    pub const fn is_valid_for_store(self) -> bool {
280        matches!(self, Self::Relaxed | Self::Release | Self::SeqCst)
281    }
282
283    /// Whether this ordering can be asked of a read-modify-write, which is any of them.
284    #[must_use]
285    pub const fn is_valid_for_rmw(self) -> bool {
286        !matches!(self, Self::NotAtomic)
287    }
288}
289
290impl fmt::Display for MemOrder {
291    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
292        f.write_str(self.name())
293    }
294}
295
296/// Which operation an `atomic_rmw` performs.
297#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
298pub enum RmwOp {
299    /// Replace, returning the old value.
300    Xchg,
301    /// Integer addition.
302    Add,
303    /// Integer subtraction.
304    Sub,
305    /// Bitwise and.
306    And,
307    /// Bitwise and, then complement, which is the one hardware sometimes has natively.
308    Nand,
309    /// Bitwise or.
310    Or,
311    /// Bitwise exclusive or.
312    Xor,
313    /// Signed maximum.
314    SMax,
315    /// Signed minimum.
316    SMin,
317    /// Unsigned maximum.
318    UMax,
319    /// Unsigned minimum.
320    UMin,
321    /// Floating point addition.
322    FAdd,
323    /// Floating point subtraction.
324    FSub,
325}
326
327impl RmwOp {
328    /// The textual form.
329    #[must_use]
330    pub const fn name(self) -> &'static str {
331        match self {
332            Self::Xchg => "xchg",
333            Self::Add => "add",
334            Self::Sub => "sub",
335            Self::And => "and",
336            Self::Nand => "nand",
337            Self::Or => "or",
338            Self::Xor => "xor",
339            Self::SMax => "smax",
340            Self::SMin => "smin",
341            Self::UMax => "umax",
342            Self::UMin => "umin",
343            Self::FAdd => "fadd",
344            Self::FSub => "fsub",
345        }
346    }
347
348    /// The operation with that name, if there is one.
349    #[must_use]
350    pub fn from_name(name: &str) -> Option<Self> {
351        Self::all().find(|op| op.name() == name)
352    }
353
354    /// Every operation.
355    pub fn all() -> impl Iterator<Item = Self> {
356        [
357            Self::Xchg,
358            Self::Add,
359            Self::Sub,
360            Self::And,
361            Self::Nand,
362            Self::Or,
363            Self::Xor,
364            Self::SMax,
365            Self::SMin,
366            Self::UMax,
367            Self::UMin,
368            Self::FAdd,
369            Self::FSub,
370        ]
371        .into_iter()
372    }
373
374    /// Whether this operates on a floating point value rather than an integer.
375    #[must_use]
376    pub const fn is_float(self) -> bool {
377        matches!(self, Self::FAdd | Self::FSub)
378    }
379}
380
381impl fmt::Display for RmwOp {
382    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
383        f.write_str(self.name())
384    }
385}
386
387/// What kind of storage a memory safety instance is, which is `class` of
388/// `spec/safe-memory/04-safety-model.md` section 4.1.
389///
390/// It is on `meta_begin` because judgement J4 writes it when the instance is created, and the
391/// one place it is read afterwards is J6: `free` is permitted on an allocated instance and on
392/// no other kind, which is what makes freeing a stack address a report rather than a crash in
393/// the allocator.
394#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
395pub enum StorageClass {
396    /// A global or a static local, which lives as long as the program does.
397    Static,
398    /// A local, which lives as long as its block does.
399    Automatic,
400    /// Storage an allocator handed out, and the only kind `free` may be given.
401    Allocated,
402    /// A mapping, from `mmap` or its equivalent.
403    Mapped,
404    /// A device register window, where a read is not a read of anything the program wrote.
405    Mmio,
406    /// Storage a device owns, which is what a DMA buffer is while the transfer runs.
407    Device,
408    /// A function, which is what the address of one points at.
409    Function,
410    /// A string or compound literal, which the implementation may have merged with another.
411    Literal,
412}
413
414impl StorageClass {
415    /// The textual form.
416    #[must_use]
417    pub const fn name(self) -> &'static str {
418        match self {
419            Self::Static => "static",
420            Self::Automatic => "automatic",
421            Self::Allocated => "allocated",
422            Self::Mapped => "mapped",
423            Self::Mmio => "mmio",
424            Self::Device => "device",
425            Self::Function => "function",
426            Self::Literal => "literal",
427        }
428    }
429
430    /// The class with that name, if there is one.
431    #[must_use]
432    pub fn from_name(name: &str) -> Option<Self> {
433        Self::all().find(|class| class.name() == name)
434    }
435
436    /// Every class, in the order document 04 lists them.
437    pub fn all() -> impl Iterator<Item = Self> {
438        [
439            Self::Static,
440            Self::Automatic,
441            Self::Allocated,
442            Self::Mapped,
443            Self::Mmio,
444            Self::Device,
445            Self::Function,
446            Self::Literal,
447        ]
448        .into_iter()
449    }
450}
451
452impl fmt::Display for StorageClass {
453    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
454        f.write_str(self.name())
455    }
456}
457
458/// Who a range of memory belongs to while it is out of the monitor's authority.
459///
460/// Judgement J7 of `spec/safe-memory/04-safety-model.md`, which is the one that has no analogue
461/// in any existing tool. A range handed to a device is a range the program must not touch until
462/// it comes back, and saying which of the three it went to is what lets the report name what the
463/// program broke rather than only that it broke something.
464#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
465pub enum Owner {
466    /// A device, which is what the DMA ownership contract hands a buffer to.
467    Device,
468    /// Code compiled without the instrumentation, per document 10.
469    Uninstrumented,
470    /// The kernel, across a system call that writes into the range.
471    Kernel,
472}
473
474impl Owner {
475    /// The textual form.
476    #[must_use]
477    pub const fn name(self) -> &'static str {
478        match self {
479            Self::Device => "device",
480            Self::Uninstrumented => "uninstrumented",
481            Self::Kernel => "kernel",
482        }
483    }
484
485    /// The owner with that name, if there is one.
486    #[must_use]
487    pub fn from_name(name: &str) -> Option<Self> {
488        Self::all().find(|owner| owner.name() == name)
489    }
490
491    /// Every owner.
492    pub fn all() -> impl Iterator<Item = Self> {
493        [Self::Device, Self::Uninstrumented, Self::Kernel].into_iter()
494    }
495}
496
497impl fmt::Display for Owner {
498    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
499        f.write_str(self.name())
500    }
501}
502
503#[cfg(test)]
504mod tests {
505    use super::*;
506
507    #[test]
508    fn a_flag_set_is_two_bytes() {
509        assert_eq!(size_of::<Flags>(), 2);
510    }
511
512    #[test]
513    fn every_flag_has_a_name_and_finds_it_again() {
514        for &(flag, name) in NAMED {
515            assert_eq!(Flags::from_name(name), Some(flag), "{name}");
516            assert_eq!(flag.to_string(), format!(".{name}"));
517        }
518        assert_eq!(Flags::from_name("poison"), None);
519        assert_eq!(Flags::from_name(""), None);
520    }
521
522    #[test]
523    fn no_two_flags_share_a_bit() {
524        let mut seen = 0u16;
525        for &(flag, name) in NAMED {
526            assert_eq!(flag.bits().count_ones(), 1, "{name} is not one bit");
527            assert_eq!(seen & flag.bits(), 0, "{name} shares a bit");
528            seen |= flag.bits();
529        }
530    }
531
532    #[test]
533    fn fast_is_exactly_the_six_fast_math_flags() {
534        let named: Vec<&str> = Flags::FAST.iter().map(|(_, name)| name).collect();
535        assert_eq!(named, ["nnan", "ninf", "nsz", "arcp", "contract", "reassoc"]);
536        assert!(!Flags::FAST.contains(Flags::NSW));
537        assert!(!Flags::FAST.contains(Flags::VOLATILE));
538    }
539
540    #[test]
541    fn the_empty_set_prints_as_nothing() {
542        assert!(Flags::NONE.is_empty());
543        assert_eq!(Flags::NONE.to_string(), "");
544        assert_eq!(Flags::NONE.iter().count(), 0);
545    }
546
547    #[test]
548    fn flags_print_as_the_suffix_the_textual_form_uses() {
549        assert_eq!((Flags::NSW | Flags::NUW).to_string(), ".nsw.nuw");
550        // Whatever order they were combined in, the printer writes them in one order, which
551        // is what a byte for byte round trip needs.
552        assert_eq!((Flags::NUW | Flags::NSW).to_string(), ".nsw.nuw");
553    }
554
555    #[test]
556    fn intersecting_is_what_a_rewrite_keeps() {
557        let one = Flags::NSW | Flags::NUW;
558        let other = Flags::NSW;
559        assert_eq!(one.intersection(other), Flags::NSW);
560        assert_eq!(one.without(Flags::NSW), Flags::NUW);
561        assert!(one.contains(Flags::NSW));
562        assert!(!other.contains(Flags::NUW));
563    }
564
565    #[test]
566    fn wrapping_flags_go_on_arithmetic_and_nowhere_else() {
567        assert!(Flags::legal_on(Opcode::Add).contains(Flags::NSW));
568        assert!(Flags::legal_on(Opcode::Shl).contains(Flags::NUW));
569        assert!(!Flags::legal_on(Opcode::Add).contains(Flags::EXACT));
570        assert!(!Flags::legal_on(Opcode::FAdd).contains(Flags::NSW));
571        assert!(!Flags::legal_on(Opcode::Load).contains(Flags::NSW));
572        assert!(Flags::legal_on(Opcode::SDiv).contains(Flags::EXACT));
573        assert!(Flags::legal_on(Opcode::FMul).contains(Flags::CONTRACT));
574        assert!(Flags::legal_on(Opcode::Store).contains(Flags::VOLATILE));
575        assert!(Flags::legal_on(Opcode::Jump).is_empty());
576    }
577
578    #[test]
579    fn nofree_goes_on_a_call_and_nowhere_else() {
580        for opcode in [Opcode::Call, Opcode::TailCall, Opcode::CallIndirect] {
581            assert!(Flags::legal_on(opcode).contains(Flags::NOFREE), "{opcode}");
582        }
583        for opcode in Opcode::all() {
584            let call = matches!(opcode, Opcode::Call | Opcode::TailCall | Opcode::CallIndirect);
585            assert_eq!(Flags::legal_on(opcode).contains(Flags::NOFREE), call, "{opcode}");
586        }
587        // It is a fact rather than a licence, so it is not part of what `-ffast-math` grants and
588        // it is not something a rewrite over arithmetic could carry onto a call.
589        assert!(!Flags::FAST.contains(Flags::NOFREE));
590    }
591
592    #[test]
593    fn every_flag_is_legal_on_something() {
594        for &(flag, name) in NAMED {
595            assert!(
596                Opcode::all().any(|op| Flags::legal_on(op).contains(flag)),
597                "{name} is legal nowhere, so nothing can ever set it"
598            );
599        }
600    }
601
602    #[test]
603    fn a_load_cannot_release_and_a_store_cannot_acquire() {
604        assert!(MemOrder::Acquire.is_valid_for_load());
605        assert!(!MemOrder::Release.is_valid_for_load());
606        assert!(!MemOrder::AcqRel.is_valid_for_load());
607        assert!(MemOrder::Release.is_valid_for_store());
608        assert!(!MemOrder::Acquire.is_valid_for_store());
609        assert!(MemOrder::SeqCst.is_valid_for_load());
610        assert!(MemOrder::SeqCst.is_valid_for_store());
611    }
612
613    #[test]
614    fn not_atomic_is_valid_for_no_atomic_operation() {
615        assert!(!MemOrder::NotAtomic.is_valid_for_load());
616        assert!(!MemOrder::NotAtomic.is_valid_for_store());
617        assert!(!MemOrder::NotAtomic.is_valid_for_rmw());
618        assert_eq!(MemOrder::default(), MemOrder::NotAtomic);
619    }
620
621    #[test]
622    fn every_ordering_and_operation_finds_its_name_again() {
623        for order in MemOrder::all() {
624            assert_eq!(MemOrder::from_name(order.name()), Some(order));
625        }
626        for op in RmwOp::all() {
627            assert_eq!(RmwOp::from_name(op.name()), Some(op));
628        }
629        assert_eq!(MemOrder::from_name("consume"), None);
630        assert_eq!(RmwOp::from_name("fmul"), None);
631    }
632
633    #[test]
634    fn the_floating_read_modify_writes_are_the_two_that_have_one() {
635        let floats: Vec<&str> = RmwOp::all().filter(|op| op.is_float()).map(RmwOp::name).collect();
636        assert_eq!(floats, ["fadd", "fsub"]);
637    }
638
639    #[test]
640    fn every_storage_class_and_owner_finds_its_name_again() {
641        for class in StorageClass::all() {
642            assert_eq!(StorageClass::from_name(class.name()), Some(class));
643        }
644        for owner in Owner::all() {
645            assert_eq!(Owner::from_name(owner.name()), Some(owner));
646        }
647        // The eight of document 04 and no more. `heap` is what a reader would guess and the
648        // model does not have it, since what the allocator hands out is `allocated`.
649        assert_eq!(StorageClass::all().count(), 8);
650        assert_eq!(StorageClass::from_name("heap"), None);
651        assert_eq!(Owner::from_name("hardware"), None);
652    }
653}