hyperlight_host/mem/
ptr_offset.rs

1/*
2Copyright 2025  The Hyperlight Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17use std::cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd};
18use std::convert::From;
19use std::ops::{Add, Sub};
20
21use tracing::{Span, instrument};
22
23use crate::Result;
24use crate::error::HyperlightError;
25
26/// An offset into a given address space.
27///
28/// Use this type to distinguish between an offset and a raw pointer
29#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
30pub(crate) struct Offset(u64);
31
32impl Offset {
33    /// Get the offset representing `0`
34    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
35    #[allow(dead_code)]
36    pub(super) fn zero() -> Self {
37        Self::default()
38    }
39
40    /// round up to the nearest multiple of `alignment`
41    #[allow(dead_code)]
42    pub(super) fn round_up_to(self, alignment: u64) -> Self {
43        let remainder = self.0 % alignment;
44        let multiples = self.0 / alignment;
45        match remainder {
46            0 => self,
47            _ => Offset::from((multiples + 1) * alignment),
48        }
49    }
50}
51
52impl Default for Offset {
53    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
54    fn default() -> Self {
55        Offset::from(0_u64)
56    }
57}
58
59impl From<u64> for Offset {
60    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
61    fn from(val: u64) -> Self {
62        Self(val)
63    }
64}
65
66impl From<&Offset> for u64 {
67    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
68    fn from(val: &Offset) -> u64 {
69        val.0
70    }
71}
72
73impl From<Offset> for u64 {
74    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
75    fn from(val: Offset) -> u64 {
76        val.0
77    }
78}
79
80impl TryFrom<Offset> for i64 {
81    type Error = HyperlightError;
82    #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
83    fn try_from(val: Offset) -> Result<i64> {
84        Ok(i64::try_from(val.0)?)
85    }
86}
87
88impl TryFrom<i64> for Offset {
89    type Error = HyperlightError;
90    #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
91    fn try_from(val: i64) -> Result<Offset> {
92        let val_u64 = u64::try_from(val)?;
93        Ok(Offset::from(val_u64))
94    }
95}
96
97impl TryFrom<usize> for Offset {
98    type Error = HyperlightError;
99    #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
100    fn try_from(val: usize) -> Result<Offset> {
101        Ok(u64::try_from(val).map(Offset::from)?)
102    }
103}
104
105/// Convert an `Offset` to a `usize`, returning an `Err` if the
106/// conversion couldn't be made.
107impl TryFrom<&Offset> for usize {
108    type Error = HyperlightError;
109    #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
110    fn try_from(val: &Offset) -> Result<usize> {
111        Ok(usize::try_from(val.0)?)
112    }
113}
114
115impl TryFrom<Offset> for usize {
116    type Error = HyperlightError;
117    #[instrument(err(Debug), skip_all, parent = Span::current(), level= "Trace")]
118    fn try_from(val: Offset) -> Result<usize> {
119        usize::try_from(&val)
120    }
121}
122
123impl Add<Offset> for Offset {
124    type Output = Offset;
125    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
126    fn add(self, rhs: Offset) -> Offset {
127        Offset::from(self.0 + rhs.0)
128    }
129}
130
131impl Add<usize> for Offset {
132    type Output = Offset;
133    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
134    fn add(self, rhs: usize) -> Offset {
135        Offset(self.0 + rhs as u64)
136    }
137}
138
139impl Add<Offset> for usize {
140    type Output = Offset;
141    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
142    fn add(self, rhs: Offset) -> Offset {
143        rhs.add(self)
144    }
145}
146
147impl Add<u64> for Offset {
148    type Output = Offset;
149    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
150    fn add(self, rhs: u64) -> Offset {
151        Offset(self.0 + rhs)
152    }
153}
154
155impl Add<Offset> for u64 {
156    type Output = Offset;
157    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
158    fn add(self, rhs: Offset) -> Offset {
159        rhs.add(self)
160    }
161}
162
163impl Sub<Offset> for Offset {
164    type Output = Offset;
165    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
166    fn sub(self, rhs: Offset) -> Offset {
167        Offset::from(self.0 - rhs.0)
168    }
169}
170
171impl Sub<usize> for Offset {
172    type Output = Offset;
173    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
174    fn sub(self, rhs: usize) -> Offset {
175        Offset(self.0 - rhs as u64)
176    }
177}
178
179impl Sub<Offset> for usize {
180    type Output = Offset;
181    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
182    fn sub(self, rhs: Offset) -> Offset {
183        rhs.sub(self)
184    }
185}
186
187impl Sub<u64> for Offset {
188    type Output = Offset;
189    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
190    fn sub(self, rhs: u64) -> Offset {
191        Offset(self.0 - rhs)
192    }
193}
194
195impl Sub<Offset> for u64 {
196    type Output = Offset;
197    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
198    fn sub(self, rhs: Offset) -> Offset {
199        rhs.sub(self)
200    }
201}
202
203impl PartialEq<usize> for Offset {
204    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
205    fn eq(&self, other: &usize) -> bool {
206        match usize::try_from(self) {
207            Ok(offset_usize) => offset_usize == *other,
208            _ => false,
209        }
210    }
211}
212
213impl PartialOrd<usize> for Offset {
214    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
215    fn partial_cmp(&self, rhs: &usize) -> Option<Ordering> {
216        match usize::try_from(self) {
217            Ok(offset_usize) if offset_usize > *rhs => Some(Ordering::Greater),
218            Ok(offset_usize) if offset_usize == *rhs => Some(Ordering::Equal),
219            Ok(_) => Some(Ordering::Less),
220            Err(_) => None,
221        }
222    }
223}
224
225impl PartialEq<u64> for Offset {
226    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
227    fn eq(&self, rhs: &u64) -> bool {
228        u64::from(self) == *rhs
229    }
230}
231
232impl PartialOrd<u64> for Offset {
233    #[instrument(skip_all, parent = Span::current(), level= "Trace")]
234    fn partial_cmp(&self, rhs: &u64) -> Option<Ordering> {
235        let lhs: u64 = self.into();
236        match lhs > *rhs {
237            true => Some(Ordering::Greater),
238            false if lhs == *rhs => Some(Ordering::Equal),
239            false => Some(Ordering::Less),
240        }
241    }
242}
243
244#[cfg(test)]
245mod tests {
246    use proptest::prelude::*;
247
248    use super::Offset;
249
250    proptest! {
251        #[test]
252        fn i64_roundtrip(i64_val in (i64::MIN..i64::MAX)) {
253            let offset_res = Offset::try_from(i64_val);
254
255            if i64_val < 0 {
256                assert!(offset_res.is_err());
257            } else {
258                assert!(offset_res.is_ok());
259                let offset = offset_res.unwrap();
260                let ret_i64_val = {
261                    let res = i64::try_from(offset);
262                    assert!(res.is_ok());
263                    res.unwrap()
264                };
265                assert_eq!(i64_val, ret_i64_val);
266            }
267        }
268        #[test]
269        fn usize_roundtrip(val in (usize::MIN..usize::MAX)) {
270            let offset = Offset::try_from(val).unwrap();
271            assert_eq!(val, usize::try_from(offset).unwrap());
272        }
273
274        #[test]
275        fn add_numeric_types(usize_val in (usize::MIN..usize::MAX), u64_val in (u64::MIN..u64::MAX)) {
276            let start = Offset::default();
277            {
278                // add usize to offset
279                assert_eq!(usize_val, usize::try_from(start + usize_val).unwrap());
280            }
281            {
282                // add u64 to offset
283                assert_eq!(u64_val, u64::from(start + u64_val));
284            }
285        }
286    }
287
288    #[test]
289    fn round_up_to() {
290        let offset = Offset::from(0);
291        let rounded = offset.round_up_to(4);
292        assert_eq!(rounded, offset);
293
294        let offset = Offset::from(1);
295        let rounded = offset.round_up_to(4);
296        assert_eq!(rounded, Offset::from(4));
297
298        let offset = Offset::from(3);
299        let rounded = offset.round_up_to(4);
300        assert_eq!(rounded, Offset::from(4));
301
302        let offset = Offset::from(4);
303        let rounded = offset.round_up_to(4);
304        assert_eq!(rounded, Offset::from(4));
305
306        let offset = Offset::from(5);
307        let rounded = offset.round_up_to(4);
308        assert_eq!(rounded, Offset::from(8));
309    }
310}