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