Skip to main content

lamina_platform/
shared.rs

1//! Types the IR and the MIR both name, defined once so the two layers agree.
2//!
3//! Each of these was once declared in `lamina-ir` and again in `lamina-mir`, which made
4//! `lamina::TlsModel` and `lamina::mir::TlsModel` distinct types with one name, bridged
5//! by hand at the lowering boundary.
6
7use std::{fmt, num::NonZeroU32};
8
9/// How a thread-local symbol's address is computed.
10///
11/// Only the two models that need no runtime support are here. General-dynamic and
12/// local-dynamic both call `__tls_get_addr`, which a freestanding target does not have,
13/// so the parser names them and refuses.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub enum TlsModel {
16    /// The offset from the thread pointer is a link-time constant. Only valid in an
17    /// executable that defines the symbol itself.
18    LocalExec,
19    /// The offset from the thread pointer is loaded from the GOT, so the symbol may be
20    /// defined by a shared object present at program start.
21    InitialExec,
22}
23
24impl TlsModel {
25    pub const fn as_str(&self) -> &'static str {
26        match self {
27            TlsModel::LocalExec => "local_exec",
28            TlsModel::InitialExec => "initial_exec",
29        }
30    }
31}
32
33impl fmt::Display for TlsModel {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        write!(f, "{}", self.as_str())
36    }
37}
38
39/// Ordering guarantees an atomic operation carries, relative to other memory
40/// operations in the same thread and across threads.
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
42pub enum MemoryOrdering {
43    /// No synchronization or ordering constraints.
44    Relaxed,
45    /// Prevents reordering of subsequent loads.
46    Acquire,
47    /// Prevents reordering of previous stores.
48    Release,
49    /// Both acquire and release semantics.
50    AcqRel,
51    /// Sequentially consistent: the strongest guarantee.
52    SeqCst,
53}
54
55impl MemoryOrdering {
56    /// Where this ordering sits in the total order the C and LLVM memory models put
57    /// their orderings in, so that one can be compared against another.
58    pub fn strength(self) -> u8 {
59        match self {
60            Self::Relaxed => 0,
61            Self::Acquire => 1,
62            Self::Release => 2,
63            Self::AcqRel => 3,
64            Self::SeqCst => 4,
65        }
66    }
67
68    /// Why `failure` cannot be the failure ordering of a compare-exchange whose
69    /// success ordering is `success`, or `None` when it can.
70    ///
71    /// A compare-exchange that fails still performs the load and nothing else, so its
72    /// ordering carries no release, and it may not order more than the success
73    /// ordering does. C and LLVM both impose these two rules.
74    pub fn cmpxchg_failure_rejection(success: Self, failure: Self) -> Option<&'static str> {
75        match failure {
76            Self::Release | Self::AcqRel => {
77                Some("the failure ordering is a load: use relaxed, acquire or seqcst")
78            }
79            Self::Relaxed | Self::Acquire | Self::SeqCst => {
80                if failure.strength() > success.strength() {
81                    Some("the failure ordering may not be stronger than the success ordering")
82                } else {
83                    None
84                }
85            }
86        }
87    }
88}
89
90impl fmt::Display for MemoryOrdering {
91    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92        let s = match self {
93            MemoryOrdering::Relaxed => "relaxed",
94            MemoryOrdering::Acquire => "acquire",
95            MemoryOrdering::Release => "release",
96            MemoryOrdering::AcqRel => "acqrel",
97            MemoryOrdering::SeqCst => "seqcst",
98        };
99        write!(f, "{s}")
100    }
101}
102
103pub const DEFAULT_SOURCE_FILE: NonZeroU32 = NonZeroU32::MIN;
104
105/// A position in the `.lamina` source an instruction was parsed from.
106#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
107pub struct SourceLoc {
108    pub line: NonZeroU32,
109    pub column: u32,
110    pub file: NonZeroU32,
111}
112
113impl SourceLoc {
114    /// A position, or `None` when `line` is zero: lines are numbered from one.
115    pub fn new(line: u32, column: u32) -> Option<Self> {
116        Self::in_file(DEFAULT_SOURCE_FILE.get(), line, column)
117    }
118
119    pub fn in_file(file: u32, line: u32, column: u32) -> Option<Self> {
120        Some(Self {
121            line: NonZeroU32::new(line)?,
122            column,
123            file: NonZeroU32::new(file)?,
124        })
125    }
126
127    pub fn line(self) -> u32 {
128        self.line.get()
129    }
130}
131
132impl fmt::Display for SourceLoc {
133    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134        if self.file == DEFAULT_SOURCE_FILE {
135            write!(f, "{}:{}", self.line, self.column)
136        } else {
137            write!(f, "{}:{}:{}", self.file, self.line, self.column)
138        }
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    #[test]
147    fn source_loc_rejects_line_zero() {
148        assert!(SourceLoc::new(0, 4).is_none());
149        let loc = SourceLoc::new(7, 4).expect("line 7 is a line");
150        assert_eq!(loc.line(), 7);
151        assert_eq!(loc.file, DEFAULT_SOURCE_FILE);
152        assert_eq!(loc.to_string(), "7:4");
153    }
154
155    #[test]
156    fn source_loc_in_a_file_names_that_file() {
157        assert!(SourceLoc::in_file(0, 3, 1).is_none());
158        assert!(SourceLoc::in_file(2, 0, 1).is_none());
159        let loc = SourceLoc::in_file(2, 3, 1).expect("file 2, line 3");
160        assert_eq!(loc.file.get(), 2);
161        assert_eq!(loc.to_string(), "2:3:1");
162        assert_ne!(loc, SourceLoc::new(3, 1).expect("line 3"));
163    }
164
165    #[test]
166    fn a_compare_exchange_failure_ordering_is_a_load_no_stronger_than_the_success() {
167        use MemoryOrdering::{AcqRel, Acquire, Relaxed, Release, SeqCst};
168        for (success, failure) in [
169            (SeqCst, SeqCst),
170            (SeqCst, Acquire),
171            (SeqCst, Relaxed),
172            (AcqRel, Acquire),
173            (Release, Acquire),
174            (Acquire, Acquire),
175            (Acquire, Relaxed),
176            (Relaxed, Relaxed),
177        ] {
178            assert_eq!(
179                MemoryOrdering::cmpxchg_failure_rejection(success, failure),
180                None,
181                "{success} / {failure}"
182            );
183        }
184        for (success, failure) in [
185            (SeqCst, Release),
186            (SeqCst, AcqRel),
187            (Relaxed, Release),
188            (Acquire, SeqCst),
189            (AcqRel, SeqCst),
190            (Relaxed, Acquire),
191        ] {
192            assert!(
193                MemoryOrdering::cmpxchg_failure_rejection(success, failure).is_some(),
194                "{success} / {failure}"
195            );
196        }
197    }
198
199    #[test]
200    fn tls_and_ordering_names_round_trip_to_text() {
201        assert_eq!(TlsModel::LocalExec.as_str(), "local_exec");
202        assert_eq!(TlsModel::InitialExec.to_string(), "initial_exec");
203        assert_eq!(MemoryOrdering::AcqRel.to_string(), "acqrel");
204        assert_eq!(MemoryOrdering::SeqCst.to_string(), "seqcst");
205    }
206}