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    /// The bytes this safety check is about lie inside one object that every call to this
94    /// function hands it, and whose extent this module knows.
95    ///
96    /// The same shape as [`Flags::STATIC`] and the next of the four sources section 7.2 lists,
97    /// which is section 7.5's summaries. A pointer that arrived as a parameter is a pointer
98    /// nothing in the function can say anything about, and it is where most of the checks a real
99    /// program keeps are. What can be said about it is said by the callers: if every call to a
100    /// function only this module can call passes a frame slot or a global with at least so many
101    /// bytes left in it, then the parameter has at least so many bytes wherever it is used.
102    ///
103    /// Worked out over the module before the pipeline starts, for the reason [`Flags::STATIC`]
104    /// gives: a call site is in a different function from the parameter it is about, and a pass is
105    /// given one function.
106    ///
107    /// It says the same two things [`Flags::STATIC`] says, an extent and a lifetime, because the
108    /// objects it is ever about are a caller's frame slot or a global and both of those are alive
109    /// for as long as the call runs. A fact rather than a licence, in the same way.
110    pub const HANDED: Self = Self(1 << 13);
111
112    /// This call hands back either null or one fresh storage instance of at least as many bytes as
113    /// its last argument asks for.
114    ///
115    /// The third of the objects whose extent is known without anybody having checked it, after the
116    /// two [`Flags::STATIC`] and [`Flags::HANDED`] are about. `malloc(n)` states the same fact an
117    /// `alloca` states, with a different instruction stating it, and the null half is why a program
118    /// has to test what it gets: a null pointer is inside no object at all, so a bounds check on one
119    /// is a check that is supposed to fail.
120    ///
121    /// On the call rather than on the checks, which is the shape [`Flags::NOFREE`] has and not the
122    /// shape the two flags above have. What has to be worked out before the pipeline starts is only
123    /// which function this call names, because resolving a name takes the interner and a pass is
124    /// handed a function and no names. Everything else, which is how many bytes and where the
125    /// program has tested for null, is read out of the function by the pass that removes the check,
126    /// and has to be: before anything has folded, `malloc(16)` is a call to `malloc` of a sign
127    /// extension of a thirty two bit sixteen.
128    ///
129    /// What it says is an extent, and never a lifetime, which is the difference from the two flags
130    /// above. A global and a caller's frame slot are alive for as long as the call runs, and an
131    /// object on the heap is alive until something frees it, which may well be this same function.
132    /// So a `free` between the allocation and the access leaves the lifetime check standing to
133    /// report the use after free.
134    ///
135    /// A fact rather than a licence, in the way [`Flags::NOFREE`] is.
136    pub const HEAP: Self = Self(1 << 14);
137
138    /// The address the check this is on is about starts where the access assumes it does.
139    ///
140    /// The alignment conjunct of judgement J1 rides on `check_bounds`, which
141    /// `spec/safe-memory/06-instrumentation.md` section 6.3 settled, so a check that goes takes the
142    /// test of it away with it and `crate::discharge` will not take one out until something has
143    /// answered it. Mostly it answers itself, off the `alloca` or the allocation the address was
144    /// computed from and the steps taken from there. This is the case it cannot: how aligned a
145    /// global is lives on the module and a pass is given one function, which is the reason
146    /// [`Flags::STATIC`] exists and the same reason repeated.
147    ///
148    /// It says the address and not the object. A global aligned to sixteen read four bytes in at a
149    /// width of four is one of these and the same global read one byte in is not, so what was
150    /// worked out before the pipeline started is the offset as well as the object.
151    ///
152    /// A fact rather than a licence, in the way [`Flags::STATIC`] is.
153    pub const ALIGNED: Self = Self(1 << 15);
154
155    /// Every fast-math flag, which is what `-ffast-math` sets on an expression.
156    pub const FAST: Self = Self(
157        Self::NNAN.0
158            | Self::NINF.0
159            | Self::NSZ.0
160            | Self::ARCP.0
161            | Self::CONTRACT.0
162            | Self::REASSOC.0,
163    );
164
165    /// The underlying bits, for the printer and for hashing an instruction.
166    #[must_use]
167    pub const fn bits(self) -> u16 {
168        self.0
169    }
170
171    /// Whether nothing is set.
172    #[must_use]
173    pub const fn is_empty(self) -> bool {
174        self.0 == 0
175    }
176
177    /// Whether every flag in `other` is set here.
178    #[must_use]
179    pub const fn contains(self, other: Self) -> bool {
180        self.0 & other.0 == other.0
181    }
182
183    /// Both sets.
184    #[must_use]
185    pub const fn union(self, other: Self) -> Self {
186        Self(self.0 | other.0)
187    }
188
189    /// The flags in both sets.
190    ///
191    /// This is what a rewrite does when it replaces two instructions with one: a licence
192    /// granted on one of them and not the other is not a licence over the result.
193    #[must_use]
194    pub const fn intersection(self, other: Self) -> Self {
195        Self(self.0 & other.0)
196    }
197
198    /// This set without the flags in `other`.
199    #[must_use]
200    pub const fn without(self, other: Self) -> Self {
201        Self(self.0 & !other.0)
202    }
203
204    /// The flags that mean anything on that opcode.
205    ///
206    /// Anything outside this is a verifier failure rather than something ignored, because a
207    /// flag on an instruction that does not read it is a flag somebody meant to put somewhere
208    /// else.
209    #[must_use]
210    pub const fn legal_on(opcode: Opcode) -> Self {
211        match opcode {
212            Opcode::Add | Opcode::Sub | Opcode::Mul | Opcode::Shl => Self::NSW.union(Self::NUW),
213            Opcode::SDiv | Opcode::UDiv | Opcode::LShr | Opcode::AShr => Self::EXACT,
214            Opcode::FAdd
215            | Opcode::FSub
216            | Opcode::FMul
217            | Opcode::FDiv
218            | Opcode::FRem
219            | Opcode::FNeg
220            | Opcode::Fma
221            | Opcode::FCmp => Self::FAST,
222            Opcode::Load | Opcode::Store | Opcode::Memcpy | Opcode::Memmove | Opcode::Memset => {
223                Self::VOLATILE
224            }
225            // On the ordered accesses as well. `volatile _Atomic int x;` is a type C allows and
226            // the two words say different things: the ordering is what other threads see and the
227            // qualifier is what the compiler may leave out, so an object can want both and an
228            // access to one carries both.
229            Opcode::AtomicLoad | Opcode::AtomicStore | Opcode::Cmpxchg | Opcode::AtomicRmw => {
230                Self::VOLATILE
231            }
232            Opcode::InlineAsm => Self::VOLATILE,
233            // On all three spellings of a call, including the indirect one. Nothing works out
234            // `nofree` for a call through an address today, and the flag is legal there because
235            // what it says is about the functions the call reaches rather than about how the call
236            // names them, so a later analysis that knows the targets has somewhere to write it.
237            //
238            // `HEAP` is on the direct call alone, because what it says is worked out from the name
239            // the call names and the other two spellings do not name one. A tail call is left out
240            // for a second reason as well: its result leaves the function, so there is nothing here
241            // that could ever be inside it.
242            Opcode::Call => Self::NOFREE.union(Self::HEAP),
243            Opcode::TailCall | Opcode::CallIndirect => Self::NOFREE,
244            // On the three checks `rucc-safety` emits and on nothing else. What they say is about
245            // the bytes a check names, so an instruction that names no bytes has no room for them.
246            Opcode::CheckLive | Opcode::CheckDeriv => Self::STATIC.union(Self::HANDED),
247            // `ALIGNED` on the bounds check alone, because the alignment conjunct rides on that
248            // one and the other two say nothing about where an access starts.
249            Opcode::CheckBounds => Self::STATIC.union(Self::HANDED).union(Self::ALIGNED),
250            Opcode::Alloca | Opcode::PtrAdd => Self::NOALIAS,
251            _ => Self::NONE,
252        }
253    }
254
255    /// Every flag that is set, with its name, in the order the printer writes them.
256    pub fn iter(self) -> impl Iterator<Item = (Self, &'static str)> {
257        NAMED.iter().copied().filter(move |&(flag, _)| self.contains(flag))
258    }
259
260    /// The flag with that name, if there is one.
261    #[must_use]
262    pub fn from_name(name: &str) -> Option<Self> {
263        NAMED.iter().find(|&&(_, named)| named == name).map(|&(flag, _)| flag)
264    }
265}
266
267impl std::ops::BitOr for Flags {
268    type Output = Self;
269
270    fn bitor(self, other: Self) -> Self {
271        self.union(other)
272    }
273}
274
275impl std::ops::BitOrAssign for Flags {
276    fn bitor_assign(&mut self, other: Self) {
277        *self = self.union(other);
278    }
279}
280
281impl fmt::Display for Flags {
282    /// The suffix form the textual IR uses, `add.nsw`, with a leading dot on each flag and
283    /// nothing at all when the set is empty.
284    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
285        for (_, name) in self.iter() {
286            write!(f, ".{name}")?;
287        }
288        Ok(())
289    }
290}
291
292impl fmt::Debug for Flags {
293    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
294        if self.is_empty() {
295            return f.write_str("Flags::NONE");
296        }
297        fmt::Display::fmt(self, f)
298    }
299}
300
301/// Each flag with its name, in printing order.
302static NAMED: &[(Flags, &str)] = &[
303    (Flags::NSW, "nsw"),
304    (Flags::NUW, "nuw"),
305    (Flags::EXACT, "exact"),
306    (Flags::NNAN, "nnan"),
307    (Flags::NINF, "ninf"),
308    (Flags::NSZ, "nsz"),
309    (Flags::ARCP, "arcp"),
310    (Flags::CONTRACT, "contract"),
311    (Flags::REASSOC, "reassoc"),
312    (Flags::VOLATILE, "volatile"),
313    (Flags::NOALIAS, "noalias"),
314    (Flags::NOFREE, "nofree"),
315    (Flags::STATIC, "static"),
316    (Flags::HANDED, "handed"),
317    (Flags::HEAP, "heap"),
318    (Flags::ALIGNED, "aligned"),
319];
320
321/// How strongly an atomic operation is ordered against everything around it.
322///
323/// These are C11's, minus `consume`, which every compiler in existence widens to `acquire`
324/// because nobody can implement it as specified and the standard committee has said so.
325#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
326pub enum MemOrder {
327    /// Not atomic at all, which is what an ordinary load or store is.
328    #[default]
329    NotAtomic,
330    /// Atomic, with no ordering against anything else.
331    Relaxed,
332    /// Nothing after this in program order moves before it.
333    Acquire,
334    /// Nothing before this in program order moves after it.
335    Release,
336    /// Both, for a read-modify-write.
337    AcqRel,
338    /// Both, and a single total order over every sequentially consistent operation.
339    SeqCst,
340}
341
342impl MemOrder {
343    /// The textual form.
344    #[must_use]
345    pub const fn name(self) -> &'static str {
346        match self {
347            Self::NotAtomic => "not_atomic",
348            Self::Relaxed => "relaxed",
349            Self::Acquire => "acquire",
350            Self::Release => "release",
351            Self::AcqRel => "acq_rel",
352            Self::SeqCst => "seq_cst",
353        }
354    }
355
356    /// The ordering with that name, if there is one.
357    #[must_use]
358    pub fn from_name(name: &str) -> Option<Self> {
359        Self::all().find(|order| order.name() == name)
360    }
361
362    /// Every ordering, weakest first.
363    pub fn all() -> impl Iterator<Item = Self> {
364        [Self::NotAtomic, Self::Relaxed, Self::Acquire, Self::Release, Self::AcqRel, Self::SeqCst]
365            .into_iter()
366    }
367
368    /// Whether this ordering can be asked of a load.
369    ///
370    /// A load cannot release, because there is nothing it published.
371    #[must_use]
372    pub const fn is_valid_for_load(self) -> bool {
373        matches!(self, Self::Relaxed | Self::Acquire | Self::SeqCst)
374    }
375
376    /// Whether this ordering can be asked of a store.
377    ///
378    /// A store cannot acquire, because it read nothing to synchronise with.
379    #[must_use]
380    pub const fn is_valid_for_store(self) -> bool {
381        matches!(self, Self::Relaxed | Self::Release | Self::SeqCst)
382    }
383
384    /// Whether this ordering can be asked of a read-modify-write, which is any of them.
385    #[must_use]
386    pub const fn is_valid_for_rmw(self) -> bool {
387        !matches!(self, Self::NotAtomic)
388    }
389
390    /// Whether this ordering publishes everything before it, which is the release half.
391    ///
392    /// Asked by the race detector rather than by the back end. A release is a synchronization edge
393    /// in the sense of `spec/safe-memory/09-type-init-and-races.md` section 9.5, so it is where a
394    /// thread's clock has to be written down for whoever takes the other end.
395    #[must_use]
396    pub const fn is_release(self) -> bool {
397        matches!(self, Self::Release | Self::AcqRel | Self::SeqCst)
398    }
399
400    /// Whether this ordering takes everything published before it, which is the acquire half.
401    ///
402    /// The other end of [`MemOrder::is_release`]. `acq_rel` and `seq_cst` answer yes to both, which
403    /// is what makes a read-modify-write one edge in and one edge out.
404    #[must_use]
405    pub const fn is_acquire(self) -> bool {
406        matches!(self, Self::Acquire | Self::AcqRel | Self::SeqCst)
407    }
408}
409
410impl fmt::Display for MemOrder {
411    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
412        f.write_str(self.name())
413    }
414}
415
416/// Which operation an `atomic_rmw` performs.
417#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
418pub enum RmwOp {
419    /// Replace, returning the old value.
420    Xchg,
421    /// Integer addition.
422    Add,
423    /// Integer subtraction.
424    Sub,
425    /// Bitwise and.
426    And,
427    /// Bitwise and, then complement, which is the one hardware sometimes has natively.
428    Nand,
429    /// Bitwise or.
430    Or,
431    /// Bitwise exclusive or.
432    Xor,
433    /// Signed maximum.
434    SMax,
435    /// Signed minimum.
436    SMin,
437    /// Unsigned maximum.
438    UMax,
439    /// Unsigned minimum.
440    UMin,
441    /// Floating point addition.
442    FAdd,
443    /// Floating point subtraction.
444    FSub,
445}
446
447impl RmwOp {
448    /// The textual form.
449    #[must_use]
450    pub const fn name(self) -> &'static str {
451        match self {
452            Self::Xchg => "xchg",
453            Self::Add => "add",
454            Self::Sub => "sub",
455            Self::And => "and",
456            Self::Nand => "nand",
457            Self::Or => "or",
458            Self::Xor => "xor",
459            Self::SMax => "smax",
460            Self::SMin => "smin",
461            Self::UMax => "umax",
462            Self::UMin => "umin",
463            Self::FAdd => "fadd",
464            Self::FSub => "fsub",
465        }
466    }
467
468    /// The operation with that name, if there is one.
469    #[must_use]
470    pub fn from_name(name: &str) -> Option<Self> {
471        Self::all().find(|op| op.name() == name)
472    }
473
474    /// Every operation.
475    pub fn all() -> impl Iterator<Item = Self> {
476        [
477            Self::Xchg,
478            Self::Add,
479            Self::Sub,
480            Self::And,
481            Self::Nand,
482            Self::Or,
483            Self::Xor,
484            Self::SMax,
485            Self::SMin,
486            Self::UMax,
487            Self::UMin,
488            Self::FAdd,
489            Self::FSub,
490        ]
491        .into_iter()
492    }
493
494    /// Whether this operates on a floating point value rather than an integer.
495    #[must_use]
496    pub const fn is_float(self) -> bool {
497        matches!(self, Self::FAdd | Self::FSub)
498    }
499}
500
501impl fmt::Display for RmwOp {
502    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
503        f.write_str(self.name())
504    }
505}
506
507/// What kind of storage a memory safety instance is, which is `class` of
508/// `spec/safe-memory/04-safety-model.md` section 4.1.
509///
510/// It is on `meta_begin` because judgement J4 writes it when the instance is created, and the
511/// one place it is read afterwards is J6: `free` is permitted on an allocated instance and on
512/// no other kind, which is what makes freeing a stack address a report rather than a crash in
513/// the allocator.
514#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
515pub enum StorageClass {
516    /// A global or a static local, which lives as long as the program does.
517    Static,
518    /// A local, which lives as long as its block does.
519    Automatic,
520    /// Storage an allocator handed out, and the only kind `free` may be given.
521    Allocated,
522    /// A mapping, from `mmap` or its equivalent.
523    Mapped,
524    /// A device register window, where a read is not a read of anything the program wrote.
525    Mmio,
526    /// Storage a device owns, which is what a DMA buffer is while the transfer runs.
527    Device,
528    /// A function, which is what the address of one points at.
529    Function,
530    /// A string or compound literal, which the implementation may have merged with another.
531    Literal,
532}
533
534impl StorageClass {
535    /// The textual form.
536    #[must_use]
537    pub const fn name(self) -> &'static str {
538        match self {
539            Self::Static => "static",
540            Self::Automatic => "automatic",
541            Self::Allocated => "allocated",
542            Self::Mapped => "mapped",
543            Self::Mmio => "mmio",
544            Self::Device => "device",
545            Self::Function => "function",
546            Self::Literal => "literal",
547        }
548    }
549
550    /// The class with that name, if there is one.
551    #[must_use]
552    pub fn from_name(name: &str) -> Option<Self> {
553        Self::all().find(|class| class.name() == name)
554    }
555
556    /// Every class, in the order document 04 lists them.
557    pub fn all() -> impl Iterator<Item = Self> {
558        [
559            Self::Static,
560            Self::Automatic,
561            Self::Allocated,
562            Self::Mapped,
563            Self::Mmio,
564            Self::Device,
565            Self::Function,
566            Self::Literal,
567        ]
568        .into_iter()
569    }
570}
571
572impl fmt::Display for StorageClass {
573    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
574        f.write_str(self.name())
575    }
576}
577
578/// Who a range of memory belongs to while it is out of the monitor's authority.
579///
580/// Judgement J7 of `spec/safe-memory/04-safety-model.md`, which is the one that has no analogue
581/// in any existing tool. A range handed to a device is a range the program must not touch until
582/// it comes back, and saying which of the three it went to is what lets the report name what the
583/// program broke rather than only that it broke something.
584#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
585pub enum Owner {
586    /// A device, which is what the DMA ownership contract hands a buffer to.
587    Device,
588    /// Code compiled without the instrumentation, per document 10.
589    Uninstrumented,
590    /// The kernel, across a system call that writes into the range.
591    Kernel,
592}
593
594impl Owner {
595    /// The textual form.
596    #[must_use]
597    pub const fn name(self) -> &'static str {
598        match self {
599            Self::Device => "device",
600            Self::Uninstrumented => "uninstrumented",
601            Self::Kernel => "kernel",
602        }
603    }
604
605    /// The owner with that name, if there is one.
606    #[must_use]
607    pub fn from_name(name: &str) -> Option<Self> {
608        Self::all().find(|owner| owner.name() == name)
609    }
610
611    /// Every owner.
612    pub fn all() -> impl Iterator<Item = Self> {
613        [Self::Device, Self::Uninstrumented, Self::Kernel].into_iter()
614    }
615}
616
617impl fmt::Display for Owner {
618    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
619        f.write_str(self.name())
620    }
621}
622
623/// What a [`crate::Opcode::Prefetch`] says about the access it is a hint for.
624///
625/// Two facts, both of them out of `__builtin_prefetch`'s own arguments, and neither of them a
626/// promise. A prefetch that says the wrong thing, or a target that ignores it, changes how long
627/// the program takes and nothing else, so nothing downstream may read either of these as a fact
628/// about what the program will do.
629#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
630pub struct PrefetchHint {
631    /// Whether the access being prepared for will write, which is the second argument.
632    ///
633    /// It matters on a machine that has separate instructions for the two, because a line fetched
634    /// for writing is fetched in a state that does not have to be asked for again when the write
635    /// happens. x86-64 has one for it, `prefetchw`, and only on a processor that says it has it,
636    /// so this reaches the back end and the back end decides.
637    pub write: bool,
638    /// How much of the data will still be wanted after the access, from zero for none of it to
639    /// three for all of it, which is the third argument.
640    ///
641    /// Three is the default and is what a program that writes the one argument form means. Zero is
642    /// the one that is a different instruction rather than a different cache level on x86-64: it
643    /// says the line is wanted once, so `prefetchnta` fetches it in a way that does not push
644    /// anything else out.
645    pub locality: u8,
646}
647
648impl PrefetchHint {
649    /// The highest locality, which is the one the one argument form of the builtin means.
650    pub const MOST: u8 = 3;
651
652    /// The hint a call that said nothing beyond the address means, which is a read that will want
653    /// all of the data afterwards.
654    #[must_use]
655    pub const fn read() -> Self {
656        Self { write: false, locality: Self::MOST }
657    }
658
659    /// The word for which half of the access this is about, which is how it prints.
660    #[must_use]
661    pub const fn access(self) -> &'static str {
662        if self.write { "write" } else { "read" }
663    }
664
665    /// Whether the locality is one of the four the builtin has.
666    ///
667    /// Asked by the verifier. Nothing here clamps an out of range one, because a number this did
668    /// not come from `__builtin_prefetch`'s third argument is a bug in whoever built the
669    /// instruction rather than something a program wrote.
670    #[must_use]
671    pub const fn is_valid(self) -> bool {
672        self.locality <= Self::MOST
673    }
674}
675
676impl fmt::Display for PrefetchHint {
677    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
678        write!(f, "{}, locality {}", self.access(), self.locality)
679    }
680}
681
682#[cfg(test)]
683mod tests {
684    use super::*;
685
686    #[test]
687    fn a_flag_set_is_two_bytes() {
688        assert_eq!(size_of::<Flags>(), 2);
689    }
690
691    #[test]
692    fn every_flag_has_a_name_and_finds_it_again() {
693        for &(flag, name) in NAMED {
694            assert_eq!(Flags::from_name(name), Some(flag), "{name}");
695            assert_eq!(flag.to_string(), format!(".{name}"));
696        }
697        assert_eq!(Flags::from_name("poison"), None);
698        assert_eq!(Flags::from_name(""), None);
699    }
700
701    #[test]
702    fn no_two_flags_share_a_bit() {
703        let mut seen = 0u16;
704        for &(flag, name) in NAMED {
705            assert_eq!(flag.bits().count_ones(), 1, "{name} is not one bit");
706            assert_eq!(seen & flag.bits(), 0, "{name} shares a bit");
707            seen |= flag.bits();
708        }
709    }
710
711    #[test]
712    fn fast_is_exactly_the_six_fast_math_flags() {
713        let named: Vec<&str> = Flags::FAST.iter().map(|(_, name)| name).collect();
714        assert_eq!(named, ["nnan", "ninf", "nsz", "arcp", "contract", "reassoc"]);
715        assert!(!Flags::FAST.contains(Flags::NSW));
716        assert!(!Flags::FAST.contains(Flags::VOLATILE));
717    }
718
719    #[test]
720    fn the_empty_set_prints_as_nothing() {
721        assert!(Flags::NONE.is_empty());
722        assert_eq!(Flags::NONE.to_string(), "");
723        assert_eq!(Flags::NONE.iter().count(), 0);
724    }
725
726    #[test]
727    fn flags_print_as_the_suffix_the_textual_form_uses() {
728        assert_eq!((Flags::NSW | Flags::NUW).to_string(), ".nsw.nuw");
729        // Whatever order they were combined in, the printer writes them in one order, which
730        // is what a byte for byte round trip needs.
731        assert_eq!((Flags::NUW | Flags::NSW).to_string(), ".nsw.nuw");
732    }
733
734    #[test]
735    fn intersecting_is_what_a_rewrite_keeps() {
736        let one = Flags::NSW | Flags::NUW;
737        let other = Flags::NSW;
738        assert_eq!(one.intersection(other), Flags::NSW);
739        assert_eq!(one.without(Flags::NSW), Flags::NUW);
740        assert!(one.contains(Flags::NSW));
741        assert!(!other.contains(Flags::NUW));
742    }
743
744    #[test]
745    fn wrapping_flags_go_on_arithmetic_and_nowhere_else() {
746        assert!(Flags::legal_on(Opcode::Add).contains(Flags::NSW));
747        assert!(Flags::legal_on(Opcode::Shl).contains(Flags::NUW));
748        assert!(!Flags::legal_on(Opcode::Add).contains(Flags::EXACT));
749        assert!(!Flags::legal_on(Opcode::FAdd).contains(Flags::NSW));
750        assert!(!Flags::legal_on(Opcode::Load).contains(Flags::NSW));
751        assert!(Flags::legal_on(Opcode::SDiv).contains(Flags::EXACT));
752        assert!(Flags::legal_on(Opcode::FMul).contains(Flags::CONTRACT));
753        assert!(Flags::legal_on(Opcode::Store).contains(Flags::VOLATILE));
754        assert!(Flags::legal_on(Opcode::Jump).is_empty());
755    }
756
757    #[test]
758    fn nofree_goes_on_a_call_and_nowhere_else() {
759        for opcode in [Opcode::Call, Opcode::TailCall, Opcode::CallIndirect] {
760            assert!(Flags::legal_on(opcode).contains(Flags::NOFREE), "{opcode}");
761        }
762        for opcode in Opcode::all() {
763            let call = matches!(opcode, Opcode::Call | Opcode::TailCall | Opcode::CallIndirect);
764            assert_eq!(Flags::legal_on(opcode).contains(Flags::NOFREE), call, "{opcode}");
765        }
766        // It is a fact rather than a licence, so it is not part of what `-ffast-math` grants and
767        // it is not something a rewrite over arithmetic could carry onto a call.
768        assert!(!Flags::FAST.contains(Flags::NOFREE));
769    }
770
771    #[test]
772    fn static_goes_on_a_safety_check_and_nowhere_else() {
773        for opcode in [Opcode::CheckBounds, Opcode::CheckLive, Opcode::CheckDeriv] {
774            assert!(Flags::legal_on(opcode).contains(Flags::STATIC), "{opcode}");
775            assert!(Flags::legal_on(opcode).contains(Flags::HANDED), "{opcode}");
776        }
777        for opcode in Opcode::all() {
778            let check =
779                matches!(opcode, Opcode::CheckBounds | Opcode::CheckLive | Opcode::CheckDeriv);
780            assert_eq!(Flags::legal_on(opcode).contains(Flags::STATIC), check, "{opcode}");
781            assert_eq!(Flags::legal_on(opcode).contains(Flags::HANDED), check, "{opcode}");
782        }
783        // The other fact, and they are legal on disjoint sets of opcodes, so an instruction that
784        // carries one can never be read as carrying the other.
785        assert!(!Flags::legal_on(Opcode::Call).contains(Flags::STATIC));
786        assert!(!Flags::legal_on(Opcode::CheckBounds).contains(Flags::NOFREE));
787    }
788
789    #[test]
790    fn heap_goes_on_the_one_call_that_names_who_it_calls() {
791        assert!(Flags::legal_on(Opcode::Call).contains(Flags::HEAP));
792        for opcode in Opcode::all() {
793            let direct = opcode == Opcode::Call;
794            assert_eq!(Flags::legal_on(opcode).contains(Flags::HEAP), direct, "{opcode}");
795        }
796        // It rides on a call the way `nofree` does rather than on a check the way the other two
797        // facts do, and a check has no room for it.
798        assert!(!Flags::legal_on(Opcode::CheckBounds).contains(Flags::HEAP));
799        assert!(!Flags::legal_on(Opcode::TailCall).contains(Flags::HEAP));
800    }
801
802    #[test]
803    fn every_flag_is_legal_on_something() {
804        for &(flag, name) in NAMED {
805            assert!(
806                Opcode::all().any(|op| Flags::legal_on(op).contains(flag)),
807                "{name} is legal nowhere, so nothing can ever set it"
808            );
809        }
810    }
811
812    #[test]
813    fn a_load_cannot_release_and_a_store_cannot_acquire() {
814        assert!(MemOrder::Acquire.is_valid_for_load());
815        assert!(!MemOrder::Release.is_valid_for_load());
816        assert!(!MemOrder::AcqRel.is_valid_for_load());
817        assert!(MemOrder::Release.is_valid_for_store());
818        assert!(!MemOrder::Acquire.is_valid_for_store());
819        assert!(MemOrder::SeqCst.is_valid_for_load());
820        assert!(MemOrder::SeqCst.is_valid_for_store());
821    }
822
823    #[test]
824    fn the_two_halves_of_an_edge_are_the_orderings_that_carry_one() {
825        let releases: Vec<&str> =
826            MemOrder::all().filter(|order| order.is_release()).map(MemOrder::name).collect();
827        assert_eq!(releases, ["release", "acq_rel", "seq_cst"]);
828        let acquires: Vec<&str> =
829            MemOrder::all().filter(|order| order.is_acquire()).map(MemOrder::name).collect();
830        assert_eq!(acquires, ["acquire", "acq_rel", "seq_cst"]);
831        // Relaxed is atomic and is not an edge, which is the distinction the race detector rests
832        // on: it says the word does not tear and it says nothing about what happened around it.
833        assert!(!MemOrder::Relaxed.is_release());
834        assert!(!MemOrder::Relaxed.is_acquire());
835    }
836
837    #[test]
838    fn not_atomic_is_valid_for_no_atomic_operation() {
839        assert!(!MemOrder::NotAtomic.is_valid_for_load());
840        assert!(!MemOrder::NotAtomic.is_valid_for_store());
841        assert!(!MemOrder::NotAtomic.is_valid_for_rmw());
842        assert_eq!(MemOrder::default(), MemOrder::NotAtomic);
843    }
844
845    #[test]
846    fn every_ordering_and_operation_finds_its_name_again() {
847        for order in MemOrder::all() {
848            assert_eq!(MemOrder::from_name(order.name()), Some(order));
849        }
850        for op in RmwOp::all() {
851            assert_eq!(RmwOp::from_name(op.name()), Some(op));
852        }
853        assert_eq!(MemOrder::from_name("consume"), None);
854        assert_eq!(RmwOp::from_name("fmul"), None);
855    }
856
857    #[test]
858    fn the_floating_read_modify_writes_are_the_two_that_have_one() {
859        let floats: Vec<&str> = RmwOp::all().filter(|op| op.is_float()).map(RmwOp::name).collect();
860        assert_eq!(floats, ["fadd", "fsub"]);
861    }
862
863    #[test]
864    fn every_storage_class_and_owner_finds_its_name_again() {
865        for class in StorageClass::all() {
866            assert_eq!(StorageClass::from_name(class.name()), Some(class));
867        }
868        for owner in Owner::all() {
869            assert_eq!(Owner::from_name(owner.name()), Some(owner));
870        }
871        // The eight of document 04 and no more. `heap` is what a reader would guess and the
872        // model does not have it, since what the allocator hands out is `allocated`.
873        assert_eq!(StorageClass::all().count(), 8);
874        assert_eq!(StorageClass::from_name("heap"), None);
875        assert_eq!(Owner::from_name("hardware"), None);
876    }
877}