Skip to main content

cranelift_codegen/ir/
sourceloc.rs

1//! Source locations.
2//!
3//! Cranelift tracks the original source location of each instruction, and preserves the source
4//! location when instructions are transformed.
5
6use core::fmt;
7#[cfg(feature = "enable-serde")]
8use serde_derive::{Deserialize, Serialize};
9
10/// A source location.
11///
12/// This is an opaque 31-bit number attached to each Cranelift IR instruction. Cranelift does not
13/// interpret source locations in any way, they are simply preserved from the input to the output.
14///
15/// The default source location uses the bit pattern `0x7fff_ffff`. It is used for instructions
16/// that can't be given a real source location. The high bit is reserved for
17/// distinguishing relative and absolute locations in [`MaybeRelSourceLoc`].
18#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
19#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
20pub struct SourceLoc(u32);
21
22impl SourceLoc {
23    /// Create a new source location with the given bits.
24    pub fn new(bits: u32) -> Self {
25        Self(bits)
26    }
27
28    /// Is this the default source location?
29    pub fn is_default(self) -> bool {
30        self == Default::default()
31    }
32
33    /// Read the bits of this source location.
34    pub fn bits(self) -> u32 {
35        self.0
36    }
37}
38
39impl Default for SourceLoc {
40    fn default() -> Self {
41        Self(0x7fff_ffff)
42    }
43}
44
45impl fmt::Display for SourceLoc {
46    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
47        if self.is_default() {
48            write!(f, "@-")
49        } else {
50            write!(f, "@{:04x}", self.0)
51        }
52    }
53}
54
55/// Source location relative to another base source location.
56#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
57#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
58pub struct RelSourceLoc(u32);
59
60impl RelSourceLoc {
61    /// Create a new relative source location with the given bits.
62    pub fn new(bits: u32) -> Self {
63        Self(bits)
64    }
65
66    /// Creates a new `RelSourceLoc` based on the given base and offset.
67    pub fn from_base_offset(base: SourceLoc, offset: SourceLoc) -> Self {
68        if base.is_default() || offset.is_default() {
69            Self::default()
70        } else {
71            // Wrap within the 31-bit source-location range, reserving the
72            // high bit for MaybeRelSourceLoc's relative/absolute tag.
73            Self(offset.bits().wrapping_sub(base.bits()) & MaybeRelSourceLoc::MASK)
74        }
75    }
76
77    /// Expands the relative source location into an absolute one, using the given base.
78    pub fn expand(&self, base: SourceLoc) -> SourceLoc {
79        if self.is_default() || base.is_default() {
80            Default::default()
81        } else {
82            SourceLoc::new(self.0.wrapping_add(base.bits()) & MaybeRelSourceLoc::MASK)
83        }
84    }
85
86    /// Is this the default relative source location?
87    pub fn is_default(self) -> bool {
88        self == Default::default()
89    }
90}
91
92impl Default for RelSourceLoc {
93    fn default() -> Self {
94        Self(0x7fff_ffff)
95    }
96}
97
98impl fmt::Display for RelSourceLoc {
99    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
100        if self.is_default() {
101            write!(f, "@-")
102        } else {
103            write!(f, "@+{:04x}", self.0)
104        }
105    }
106}
107
108/// A source location that is either a `RelSourceLoc` or `SourceLoc`.
109///
110/// This is used to represent a source location in the `MachBuffer`
111/// that is initially relative to some base and is later relocated. We
112/// do this to permit better code caching during incremental
113/// compilation: the MachBuffer records the first SourceLoc it is
114/// given as a base, and if the same function IR is later compiled but
115/// with a different starting SourceLoc, we can reuse the cached
116/// compilation result and just relocate (offset) the SourceLocs.
117///
118/// This relocation is an in-place update pass, so we want a "union"
119/// type, essentially, but we don't want to pay the overhead of a true
120/// `enum` (8 bytes rather than 4 in a large array), so we bitpack
121/// this representation.
122#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
123#[cfg_attr(
124    feature = "enable-serde",
125    derive(serde_derive::Serialize, serde_derive::Deserialize)
126)]
127pub struct MaybeRelSourceLoc(u32);
128
129impl MaybeRelSourceLoc {
130    const REL_BIT: u32 = 0x8000_0000;
131    const MASK: u32 = !Self::REL_BIT;
132
133    /// Create a relative SourceLoc.
134    pub fn rel(loc: RelSourceLoc) -> Self {
135        debug_assert!(loc.0 & Self::MASK == loc.0);
136        MaybeRelSourceLoc(loc.0 | Self::REL_BIT)
137    }
138
139    /// Create an absolute SourceLoc.
140    pub fn abs(loc: SourceLoc) -> Self {
141        debug_assert!(loc.0 & Self::MASK == loc.0);
142        MaybeRelSourceLoc(loc.0)
143    }
144
145    /// Is this a relative SourceLoc?
146    pub fn is_rel(&self) -> bool {
147        self.0 & Self::REL_BIT != 0
148    }
149
150    /// Is this an absolute SourceLoc?
151    pub fn is_abs(&self) -> bool {
152        self.0 & Self::REL_BIT == 0
153    }
154
155    /// Unwrap a relative SourceLoc.
156    ///
157    /// # Panics
158    ///
159    /// Panics if this is not a relative SourceLoc.
160    pub fn as_rel(&self) -> RelSourceLoc {
161        assert!(self.is_rel());
162        RelSourceLoc(self.0 & Self::MASK)
163    }
164
165    /// Unwrap an absolute SourceLoc.
166    ///
167    /// # Panics
168    ///
169    /// Panics if this is not an absolute SourceLoc.
170    pub fn as_abs(&self) -> SourceLoc {
171        assert!(self.is_abs());
172        SourceLoc(self.0)
173    }
174
175    /// Map a relative to an absolute SourceLoc, given another
176    /// absolute SourceLoc as a base.
177    ///
178    /// # Panics
179    ///
180    /// Panics if this is not a relative SourceLoc.
181    pub fn relocate(&self, base: SourceLoc) -> SourceLoc {
182        self.as_rel().expand(base)
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use crate::ir::SourceLoc;
189    use alloc::string::ToString;
190
191    #[test]
192    fn display() {
193        assert_eq!(SourceLoc::default().to_string(), "@-");
194        assert_eq!(SourceLoc::new(0).to_string(), "@0000");
195        assert_eq!(SourceLoc::new(16).to_string(), "@0010");
196        assert_eq!(SourceLoc::new(0xabcdef).to_string(), "@abcdef");
197    }
198}