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 /// Every fast-math flag, which is what `-ffast-math` sets on an expression.
139 pub const FAST: Self = Self(
140 Self::NNAN.0
141 | Self::NINF.0
142 | Self::NSZ.0
143 | Self::ARCP.0
144 | Self::CONTRACT.0
145 | Self::REASSOC.0,
146 );
147
148 /// The underlying bits, for the printer and for hashing an instruction.
149 #[must_use]
150 pub const fn bits(self) -> u16 {
151 self.0
152 }
153
154 /// Whether nothing is set.
155 #[must_use]
156 pub const fn is_empty(self) -> bool {
157 self.0 == 0
158 }
159
160 /// Whether every flag in `other` is set here.
161 #[must_use]
162 pub const fn contains(self, other: Self) -> bool {
163 self.0 & other.0 == other.0
164 }
165
166 /// Both sets.
167 #[must_use]
168 pub const fn union(self, other: Self) -> Self {
169 Self(self.0 | other.0)
170 }
171
172 /// The flags in both sets.
173 ///
174 /// This is what a rewrite does when it replaces two instructions with one: a licence
175 /// granted on one of them and not the other is not a licence over the result.
176 #[must_use]
177 pub const fn intersection(self, other: Self) -> Self {
178 Self(self.0 & other.0)
179 }
180
181 /// This set without the flags in `other`.
182 #[must_use]
183 pub const fn without(self, other: Self) -> Self {
184 Self(self.0 & !other.0)
185 }
186
187 /// The flags that mean anything on that opcode.
188 ///
189 /// Anything outside this is a verifier failure rather than something ignored, because a
190 /// flag on an instruction that does not read it is a flag somebody meant to put somewhere
191 /// else.
192 #[must_use]
193 pub const fn legal_on(opcode: Opcode) -> Self {
194 match opcode {
195 Opcode::Add | Opcode::Sub | Opcode::Mul | Opcode::Shl => Self::NSW.union(Self::NUW),
196 Opcode::SDiv | Opcode::UDiv | Opcode::LShr | Opcode::AShr => Self::EXACT,
197 Opcode::FAdd
198 | Opcode::FSub
199 | Opcode::FMul
200 | Opcode::FDiv
201 | Opcode::FRem
202 | Opcode::FNeg
203 | Opcode::Fma
204 | Opcode::FCmp => Self::FAST,
205 Opcode::Load | Opcode::Store | Opcode::Memcpy | Opcode::Memmove | Opcode::Memset => {
206 Self::VOLATILE
207 }
208 // On the ordered accesses as well. `volatile _Atomic int x;` is a type C allows and
209 // the two words say different things: the ordering is what other threads see and the
210 // qualifier is what the compiler may leave out, so an object can want both and an
211 // access to one carries both.
212 Opcode::AtomicLoad | Opcode::AtomicStore | Opcode::Cmpxchg | Opcode::AtomicRmw => {
213 Self::VOLATILE
214 }
215 Opcode::InlineAsm => Self::VOLATILE,
216 // On all three spellings of a call, including the indirect one. Nothing works out
217 // `nofree` for a call through an address today, and the flag is legal there because
218 // what it says is about the functions the call reaches rather than about how the call
219 // names them, so a later analysis that knows the targets has somewhere to write it.
220 //
221 // `HEAP` is on the direct call alone, because what it says is worked out from the name
222 // the call names and the other two spellings do not name one. A tail call is left out
223 // for a second reason as well: its result leaves the function, so there is nothing here
224 // that could ever be inside it.
225 Opcode::Call => Self::NOFREE.union(Self::HEAP),
226 Opcode::TailCall | Opcode::CallIndirect => Self::NOFREE,
227 // On the three checks `rucc-safety` emits and on nothing else. What they say is about
228 // the bytes a check names, so an instruction that names no bytes has no room for them.
229 Opcode::CheckBounds | Opcode::CheckLive | Opcode::CheckDeriv => {
230 Self::STATIC.union(Self::HANDED)
231 }
232 Opcode::Alloca | Opcode::PtrAdd => Self::NOALIAS,
233 _ => Self::NONE,
234 }
235 }
236
237 /// Every flag that is set, with its name, in the order the printer writes them.
238 pub fn iter(self) -> impl Iterator<Item = (Self, &'static str)> {
239 NAMED.iter().copied().filter(move |&(flag, _)| self.contains(flag))
240 }
241
242 /// The flag with that name, if there is one.
243 #[must_use]
244 pub fn from_name(name: &str) -> Option<Self> {
245 NAMED.iter().find(|&&(_, named)| named == name).map(|&(flag, _)| flag)
246 }
247}
248
249impl std::ops::BitOr for Flags {
250 type Output = Self;
251
252 fn bitor(self, other: Self) -> Self {
253 self.union(other)
254 }
255}
256
257impl std::ops::BitOrAssign for Flags {
258 fn bitor_assign(&mut self, other: Self) {
259 *self = self.union(other);
260 }
261}
262
263impl fmt::Display for Flags {
264 /// The suffix form the textual IR uses, `add.nsw`, with a leading dot on each flag and
265 /// nothing at all when the set is empty.
266 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
267 for (_, name) in self.iter() {
268 write!(f, ".{name}")?;
269 }
270 Ok(())
271 }
272}
273
274impl fmt::Debug for Flags {
275 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276 if self.is_empty() {
277 return f.write_str("Flags::NONE");
278 }
279 fmt::Display::fmt(self, f)
280 }
281}
282
283/// Each flag with its name, in printing order.
284static NAMED: &[(Flags, &str)] = &[
285 (Flags::NSW, "nsw"),
286 (Flags::NUW, "nuw"),
287 (Flags::EXACT, "exact"),
288 (Flags::NNAN, "nnan"),
289 (Flags::NINF, "ninf"),
290 (Flags::NSZ, "nsz"),
291 (Flags::ARCP, "arcp"),
292 (Flags::CONTRACT, "contract"),
293 (Flags::REASSOC, "reassoc"),
294 (Flags::VOLATILE, "volatile"),
295 (Flags::NOALIAS, "noalias"),
296 (Flags::NOFREE, "nofree"),
297 (Flags::STATIC, "static"),
298 (Flags::HANDED, "handed"),
299 (Flags::HEAP, "heap"),
300];
301
302/// How strongly an atomic operation is ordered against everything around it.
303///
304/// These are C11's, minus `consume`, which every compiler in existence widens to `acquire`
305/// because nobody can implement it as specified and the standard committee has said so.
306#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
307pub enum MemOrder {
308 /// Not atomic at all, which is what an ordinary load or store is.
309 #[default]
310 NotAtomic,
311 /// Atomic, with no ordering against anything else.
312 Relaxed,
313 /// Nothing after this in program order moves before it.
314 Acquire,
315 /// Nothing before this in program order moves after it.
316 Release,
317 /// Both, for a read-modify-write.
318 AcqRel,
319 /// Both, and a single total order over every sequentially consistent operation.
320 SeqCst,
321}
322
323impl MemOrder {
324 /// The textual form.
325 #[must_use]
326 pub const fn name(self) -> &'static str {
327 match self {
328 Self::NotAtomic => "not_atomic",
329 Self::Relaxed => "relaxed",
330 Self::Acquire => "acquire",
331 Self::Release => "release",
332 Self::AcqRel => "acq_rel",
333 Self::SeqCst => "seq_cst",
334 }
335 }
336
337 /// The ordering with that name, if there is one.
338 #[must_use]
339 pub fn from_name(name: &str) -> Option<Self> {
340 Self::all().find(|order| order.name() == name)
341 }
342
343 /// Every ordering, weakest first.
344 pub fn all() -> impl Iterator<Item = Self> {
345 [Self::NotAtomic, Self::Relaxed, Self::Acquire, Self::Release, Self::AcqRel, Self::SeqCst]
346 .into_iter()
347 }
348
349 /// Whether this ordering can be asked of a load.
350 ///
351 /// A load cannot release, because there is nothing it published.
352 #[must_use]
353 pub const fn is_valid_for_load(self) -> bool {
354 matches!(self, Self::Relaxed | Self::Acquire | Self::SeqCst)
355 }
356
357 /// Whether this ordering can be asked of a store.
358 ///
359 /// A store cannot acquire, because it read nothing to synchronise with.
360 #[must_use]
361 pub const fn is_valid_for_store(self) -> bool {
362 matches!(self, Self::Relaxed | Self::Release | Self::SeqCst)
363 }
364
365 /// Whether this ordering can be asked of a read-modify-write, which is any of them.
366 #[must_use]
367 pub const fn is_valid_for_rmw(self) -> bool {
368 !matches!(self, Self::NotAtomic)
369 }
370}
371
372impl fmt::Display for MemOrder {
373 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
374 f.write_str(self.name())
375 }
376}
377
378/// Which operation an `atomic_rmw` performs.
379#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
380pub enum RmwOp {
381 /// Replace, returning the old value.
382 Xchg,
383 /// Integer addition.
384 Add,
385 /// Integer subtraction.
386 Sub,
387 /// Bitwise and.
388 And,
389 /// Bitwise and, then complement, which is the one hardware sometimes has natively.
390 Nand,
391 /// Bitwise or.
392 Or,
393 /// Bitwise exclusive or.
394 Xor,
395 /// Signed maximum.
396 SMax,
397 /// Signed minimum.
398 SMin,
399 /// Unsigned maximum.
400 UMax,
401 /// Unsigned minimum.
402 UMin,
403 /// Floating point addition.
404 FAdd,
405 /// Floating point subtraction.
406 FSub,
407}
408
409impl RmwOp {
410 /// The textual form.
411 #[must_use]
412 pub const fn name(self) -> &'static str {
413 match self {
414 Self::Xchg => "xchg",
415 Self::Add => "add",
416 Self::Sub => "sub",
417 Self::And => "and",
418 Self::Nand => "nand",
419 Self::Or => "or",
420 Self::Xor => "xor",
421 Self::SMax => "smax",
422 Self::SMin => "smin",
423 Self::UMax => "umax",
424 Self::UMin => "umin",
425 Self::FAdd => "fadd",
426 Self::FSub => "fsub",
427 }
428 }
429
430 /// The operation with that name, if there is one.
431 #[must_use]
432 pub fn from_name(name: &str) -> Option<Self> {
433 Self::all().find(|op| op.name() == name)
434 }
435
436 /// Every operation.
437 pub fn all() -> impl Iterator<Item = Self> {
438 [
439 Self::Xchg,
440 Self::Add,
441 Self::Sub,
442 Self::And,
443 Self::Nand,
444 Self::Or,
445 Self::Xor,
446 Self::SMax,
447 Self::SMin,
448 Self::UMax,
449 Self::UMin,
450 Self::FAdd,
451 Self::FSub,
452 ]
453 .into_iter()
454 }
455
456 /// Whether this operates on a floating point value rather than an integer.
457 #[must_use]
458 pub const fn is_float(self) -> bool {
459 matches!(self, Self::FAdd | Self::FSub)
460 }
461}
462
463impl fmt::Display for RmwOp {
464 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
465 f.write_str(self.name())
466 }
467}
468
469/// What kind of storage a memory safety instance is, which is `class` of
470/// `spec/safe-memory/04-safety-model.md` section 4.1.
471///
472/// It is on `meta_begin` because judgement J4 writes it when the instance is created, and the
473/// one place it is read afterwards is J6: `free` is permitted on an allocated instance and on
474/// no other kind, which is what makes freeing a stack address a report rather than a crash in
475/// the allocator.
476#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
477pub enum StorageClass {
478 /// A global or a static local, which lives as long as the program does.
479 Static,
480 /// A local, which lives as long as its block does.
481 Automatic,
482 /// Storage an allocator handed out, and the only kind `free` may be given.
483 Allocated,
484 /// A mapping, from `mmap` or its equivalent.
485 Mapped,
486 /// A device register window, where a read is not a read of anything the program wrote.
487 Mmio,
488 /// Storage a device owns, which is what a DMA buffer is while the transfer runs.
489 Device,
490 /// A function, which is what the address of one points at.
491 Function,
492 /// A string or compound literal, which the implementation may have merged with another.
493 Literal,
494}
495
496impl StorageClass {
497 /// The textual form.
498 #[must_use]
499 pub const fn name(self) -> &'static str {
500 match self {
501 Self::Static => "static",
502 Self::Automatic => "automatic",
503 Self::Allocated => "allocated",
504 Self::Mapped => "mapped",
505 Self::Mmio => "mmio",
506 Self::Device => "device",
507 Self::Function => "function",
508 Self::Literal => "literal",
509 }
510 }
511
512 /// The class with that name, if there is one.
513 #[must_use]
514 pub fn from_name(name: &str) -> Option<Self> {
515 Self::all().find(|class| class.name() == name)
516 }
517
518 /// Every class, in the order document 04 lists them.
519 pub fn all() -> impl Iterator<Item = Self> {
520 [
521 Self::Static,
522 Self::Automatic,
523 Self::Allocated,
524 Self::Mapped,
525 Self::Mmio,
526 Self::Device,
527 Self::Function,
528 Self::Literal,
529 ]
530 .into_iter()
531 }
532}
533
534impl fmt::Display for StorageClass {
535 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
536 f.write_str(self.name())
537 }
538}
539
540/// Who a range of memory belongs to while it is out of the monitor's authority.
541///
542/// Judgement J7 of `spec/safe-memory/04-safety-model.md`, which is the one that has no analogue
543/// in any existing tool. A range handed to a device is a range the program must not touch until
544/// it comes back, and saying which of the three it went to is what lets the report name what the
545/// program broke rather than only that it broke something.
546#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
547pub enum Owner {
548 /// A device, which is what the DMA ownership contract hands a buffer to.
549 Device,
550 /// Code compiled without the instrumentation, per document 10.
551 Uninstrumented,
552 /// The kernel, across a system call that writes into the range.
553 Kernel,
554}
555
556impl Owner {
557 /// The textual form.
558 #[must_use]
559 pub const fn name(self) -> &'static str {
560 match self {
561 Self::Device => "device",
562 Self::Uninstrumented => "uninstrumented",
563 Self::Kernel => "kernel",
564 }
565 }
566
567 /// The owner with that name, if there is one.
568 #[must_use]
569 pub fn from_name(name: &str) -> Option<Self> {
570 Self::all().find(|owner| owner.name() == name)
571 }
572
573 /// Every owner.
574 pub fn all() -> impl Iterator<Item = Self> {
575 [Self::Device, Self::Uninstrumented, Self::Kernel].into_iter()
576 }
577}
578
579impl fmt::Display for Owner {
580 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
581 f.write_str(self.name())
582 }
583}
584
585#[cfg(test)]
586mod tests {
587 use super::*;
588
589 #[test]
590 fn a_flag_set_is_two_bytes() {
591 assert_eq!(size_of::<Flags>(), 2);
592 }
593
594 #[test]
595 fn every_flag_has_a_name_and_finds_it_again() {
596 for &(flag, name) in NAMED {
597 assert_eq!(Flags::from_name(name), Some(flag), "{name}");
598 assert_eq!(flag.to_string(), format!(".{name}"));
599 }
600 assert_eq!(Flags::from_name("poison"), None);
601 assert_eq!(Flags::from_name(""), None);
602 }
603
604 #[test]
605 fn no_two_flags_share_a_bit() {
606 let mut seen = 0u16;
607 for &(flag, name) in NAMED {
608 assert_eq!(flag.bits().count_ones(), 1, "{name} is not one bit");
609 assert_eq!(seen & flag.bits(), 0, "{name} shares a bit");
610 seen |= flag.bits();
611 }
612 }
613
614 #[test]
615 fn fast_is_exactly_the_six_fast_math_flags() {
616 let named: Vec<&str> = Flags::FAST.iter().map(|(_, name)| name).collect();
617 assert_eq!(named, ["nnan", "ninf", "nsz", "arcp", "contract", "reassoc"]);
618 assert!(!Flags::FAST.contains(Flags::NSW));
619 assert!(!Flags::FAST.contains(Flags::VOLATILE));
620 }
621
622 #[test]
623 fn the_empty_set_prints_as_nothing() {
624 assert!(Flags::NONE.is_empty());
625 assert_eq!(Flags::NONE.to_string(), "");
626 assert_eq!(Flags::NONE.iter().count(), 0);
627 }
628
629 #[test]
630 fn flags_print_as_the_suffix_the_textual_form_uses() {
631 assert_eq!((Flags::NSW | Flags::NUW).to_string(), ".nsw.nuw");
632 // Whatever order they were combined in, the printer writes them in one order, which
633 // is what a byte for byte round trip needs.
634 assert_eq!((Flags::NUW | Flags::NSW).to_string(), ".nsw.nuw");
635 }
636
637 #[test]
638 fn intersecting_is_what_a_rewrite_keeps() {
639 let one = Flags::NSW | Flags::NUW;
640 let other = Flags::NSW;
641 assert_eq!(one.intersection(other), Flags::NSW);
642 assert_eq!(one.without(Flags::NSW), Flags::NUW);
643 assert!(one.contains(Flags::NSW));
644 assert!(!other.contains(Flags::NUW));
645 }
646
647 #[test]
648 fn wrapping_flags_go_on_arithmetic_and_nowhere_else() {
649 assert!(Flags::legal_on(Opcode::Add).contains(Flags::NSW));
650 assert!(Flags::legal_on(Opcode::Shl).contains(Flags::NUW));
651 assert!(!Flags::legal_on(Opcode::Add).contains(Flags::EXACT));
652 assert!(!Flags::legal_on(Opcode::FAdd).contains(Flags::NSW));
653 assert!(!Flags::legal_on(Opcode::Load).contains(Flags::NSW));
654 assert!(Flags::legal_on(Opcode::SDiv).contains(Flags::EXACT));
655 assert!(Flags::legal_on(Opcode::FMul).contains(Flags::CONTRACT));
656 assert!(Flags::legal_on(Opcode::Store).contains(Flags::VOLATILE));
657 assert!(Flags::legal_on(Opcode::Jump).is_empty());
658 }
659
660 #[test]
661 fn nofree_goes_on_a_call_and_nowhere_else() {
662 for opcode in [Opcode::Call, Opcode::TailCall, Opcode::CallIndirect] {
663 assert!(Flags::legal_on(opcode).contains(Flags::NOFREE), "{opcode}");
664 }
665 for opcode in Opcode::all() {
666 let call = matches!(opcode, Opcode::Call | Opcode::TailCall | Opcode::CallIndirect);
667 assert_eq!(Flags::legal_on(opcode).contains(Flags::NOFREE), call, "{opcode}");
668 }
669 // It is a fact rather than a licence, so it is not part of what `-ffast-math` grants and
670 // it is not something a rewrite over arithmetic could carry onto a call.
671 assert!(!Flags::FAST.contains(Flags::NOFREE));
672 }
673
674 #[test]
675 fn static_goes_on_a_safety_check_and_nowhere_else() {
676 for opcode in [Opcode::CheckBounds, Opcode::CheckLive, Opcode::CheckDeriv] {
677 assert!(Flags::legal_on(opcode).contains(Flags::STATIC), "{opcode}");
678 assert!(Flags::legal_on(opcode).contains(Flags::HANDED), "{opcode}");
679 }
680 for opcode in Opcode::all() {
681 let check =
682 matches!(opcode, Opcode::CheckBounds | Opcode::CheckLive | Opcode::CheckDeriv);
683 assert_eq!(Flags::legal_on(opcode).contains(Flags::STATIC), check, "{opcode}");
684 assert_eq!(Flags::legal_on(opcode).contains(Flags::HANDED), check, "{opcode}");
685 }
686 // The other fact, and they are legal on disjoint sets of opcodes, so an instruction that
687 // carries one can never be read as carrying the other.
688 assert!(!Flags::legal_on(Opcode::Call).contains(Flags::STATIC));
689 assert!(!Flags::legal_on(Opcode::CheckBounds).contains(Flags::NOFREE));
690 }
691
692 #[test]
693 fn heap_goes_on_the_one_call_that_names_who_it_calls() {
694 assert!(Flags::legal_on(Opcode::Call).contains(Flags::HEAP));
695 for opcode in Opcode::all() {
696 let direct = opcode == Opcode::Call;
697 assert_eq!(Flags::legal_on(opcode).contains(Flags::HEAP), direct, "{opcode}");
698 }
699 // It rides on a call the way `nofree` does rather than on a check the way the other two
700 // facts do, and a check has no room for it.
701 assert!(!Flags::legal_on(Opcode::CheckBounds).contains(Flags::HEAP));
702 assert!(!Flags::legal_on(Opcode::TailCall).contains(Flags::HEAP));
703 }
704
705 #[test]
706 fn every_flag_is_legal_on_something() {
707 for &(flag, name) in NAMED {
708 assert!(
709 Opcode::all().any(|op| Flags::legal_on(op).contains(flag)),
710 "{name} is legal nowhere, so nothing can ever set it"
711 );
712 }
713 }
714
715 #[test]
716 fn a_load_cannot_release_and_a_store_cannot_acquire() {
717 assert!(MemOrder::Acquire.is_valid_for_load());
718 assert!(!MemOrder::Release.is_valid_for_load());
719 assert!(!MemOrder::AcqRel.is_valid_for_load());
720 assert!(MemOrder::Release.is_valid_for_store());
721 assert!(!MemOrder::Acquire.is_valid_for_store());
722 assert!(MemOrder::SeqCst.is_valid_for_load());
723 assert!(MemOrder::SeqCst.is_valid_for_store());
724 }
725
726 #[test]
727 fn not_atomic_is_valid_for_no_atomic_operation() {
728 assert!(!MemOrder::NotAtomic.is_valid_for_load());
729 assert!(!MemOrder::NotAtomic.is_valid_for_store());
730 assert!(!MemOrder::NotAtomic.is_valid_for_rmw());
731 assert_eq!(MemOrder::default(), MemOrder::NotAtomic);
732 }
733
734 #[test]
735 fn every_ordering_and_operation_finds_its_name_again() {
736 for order in MemOrder::all() {
737 assert_eq!(MemOrder::from_name(order.name()), Some(order));
738 }
739 for op in RmwOp::all() {
740 assert_eq!(RmwOp::from_name(op.name()), Some(op));
741 }
742 assert_eq!(MemOrder::from_name("consume"), None);
743 assert_eq!(RmwOp::from_name("fmul"), None);
744 }
745
746 #[test]
747 fn the_floating_read_modify_writes_are_the_two_that_have_one() {
748 let floats: Vec<&str> = RmwOp::all().filter(|op| op.is_float()).map(RmwOp::name).collect();
749 assert_eq!(floats, ["fadd", "fsub"]);
750 }
751
752 #[test]
753 fn every_storage_class_and_owner_finds_its_name_again() {
754 for class in StorageClass::all() {
755 assert_eq!(StorageClass::from_name(class.name()), Some(class));
756 }
757 for owner in Owner::all() {
758 assert_eq!(Owner::from_name(owner.name()), Some(owner));
759 }
760 // The eight of document 04 and no more. `heap` is what a reader would guess and the
761 // model does not have it, since what the allocator hands out is `allocated`.
762 assert_eq!(StorageClass::all().count(), 8);
763 assert_eq!(StorageClass::from_name("heap"), None);
764 assert_eq!(Owner::from_name("hardware"), None);
765 }
766}