Skip to main content

hyperlight_host/mem/
ptr_offset.rs

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