lucet_runtime_internals/
val.rs

1//! Typed values for passing into and returning from sandboxed
2//! programs.
3
4use libc::c_void;
5use std::arch::x86_64::{
6    __m128, _mm_castpd_ps, _mm_castps_pd, _mm_load_pd1, _mm_load_ps1, _mm_setzero_ps,
7    _mm_storeu_pd, _mm_storeu_ps,
8};
9
10use lucet_module::ValueType;
11
12impl Val {
13    pub fn value_type(&self) -> ValueType {
14        match self {
15            // USize, ISize, and CPtr are all as fits for definitions on the target architecture
16            // (wasm) which is all 32-bit.
17            Val::USize(_) | Val::ISize(_) | Val::CPtr(_) => ValueType::I32,
18            Val::GuestPtr(_) => ValueType::I32,
19            Val::I8(_) | Val::U8(_) | Val::I16(_) | Val::U16(_) | Val::I32(_) | Val::U32(_) => {
20                ValueType::I32
21            }
22            Val::I64(_) | Val::U64(_) => ValueType::I64,
23            Val::Bool(_) => ValueType::I32,
24            Val::F32(_) => ValueType::F32,
25            Val::F64(_) => ValueType::F64,
26        }
27    }
28}
29
30/// Typed values used for passing arguments into guest functions.
31#[derive(Clone, Copy, Debug)]
32pub enum Val {
33    CPtr(*const c_void),
34    /// A WebAssembly linear memory address
35    GuestPtr(u32),
36    U8(u8),
37    U16(u16),
38    U32(u32),
39    U64(u64),
40    I8(i8),
41    I16(i16),
42    I32(i32),
43    I64(i64),
44    USize(usize),
45    ISize(isize),
46    Bool(bool),
47    F32(f32),
48    F64(f64),
49}
50
51// the pointer variant is just a wrapper; the caller will know they're still responsible for their
52// safety
53unsafe impl Send for Val {}
54unsafe impl Sync for Val {}
55
56impl<T> From<*const T> for Val {
57    fn from(x: *const T) -> Val {
58        Val::CPtr(x as *const c_void)
59    }
60}
61
62impl<T> From<*mut T> for Val {
63    fn from(x: *mut T) -> Val {
64        Val::CPtr(x as *mut c_void)
65    }
66}
67
68macro_rules! impl_from_scalars {
69    ( { $( $ctor:ident : $ty:ty ),* } ) => {
70        $(
71            impl From<$ty> for Val {
72                fn from(x: $ty) -> Val {
73                    Val::$ctor(x)
74                }
75            }
76        )*
77    };
78}
79
80// Since there is overlap in these enum variants, we can't have instances for all of them, such as
81// GuestPtr
82impl_from_scalars!({
83    U8: u8,
84    U16: u16,
85    U32: u32,
86    U64: u64,
87    I8: i8,
88    I16: i16,
89    I32: i32,
90    I64: i64,
91    USize: usize,
92    ISize: isize,
93    Bool: bool,
94    F32: f32,
95    F64: f64
96});
97
98/// Register representation of `Val`.
99///
100/// When mapping `Val`s to x86_64 registers, we map floating point
101/// values into the SSE registers _xmmN_, and all other values into
102/// general-purpose (integer) registers.
103pub enum RegVal {
104    GpReg(u64),
105    FpReg(__m128),
106}
107
108/// Convert a `Val` to its representation when stored in an
109/// argument register.
110pub fn val_to_reg(val: &Val) -> RegVal {
111    use self::RegVal::*;
112    use self::Val::*;
113    match *val {
114        CPtr(v) => GpReg(v as u64),
115        GuestPtr(v) => GpReg(v as u64),
116        U8(v) => GpReg(v as u64),
117        U16(v) => GpReg(v as u64),
118        U32(v) => GpReg(v as u64),
119        U64(v) => GpReg(v as u64),
120        I8(v) => GpReg(v as u64),
121        I16(v) => GpReg(v as u64),
122        I32(v) => GpReg(v as u64),
123        I64(v) => GpReg(v as u64),
124        USize(v) => GpReg(v as u64),
125        ISize(v) => GpReg(v as u64),
126        Bool(false) => GpReg(0u64),
127        Bool(true) => GpReg(1u64),
128        Val::F32(v) => FpReg(unsafe { _mm_load_ps1(&v as *const f32) }),
129        Val::F64(v) => FpReg(unsafe { _mm_castpd_ps(_mm_load_pd1(&v as *const f64)) }),
130    }
131}
132
133/// Convert a `Val` to its representation when spilled onto the
134/// stack.
135pub fn val_to_stack(val: &Val) -> u64 {
136    use self::Val::*;
137    match *val {
138        CPtr(v) => v as u64,
139        GuestPtr(v) => v as u64,
140        U8(v) => v as u64,
141        U16(v) => v as u64,
142        U32(v) => v as u64,
143        U64(v) => v as u64,
144        I8(v) => v as u64,
145        I16(v) => v as u64,
146        I32(v) => v as u64,
147        I64(v) => v as u64,
148        USize(v) => v as u64,
149        ISize(v) => v as u64,
150        Bool(false) => 0u64,
151        Bool(true) => 1u64,
152        F32(v) => v.to_bits() as u64,
153        F64(v) => v.to_bits(),
154    }
155}
156
157/// A value returned by a guest function.
158///
159/// Since the Rust type system cannot know the type of the returned value, the user must use the
160/// appropriate `From` implementation or `as_T` method.
161#[derive(Clone, Copy, Debug)]
162pub struct UntypedRetVal {
163    fp: __m128,
164    gp: u64,
165}
166
167impl std::fmt::Display for UntypedRetVal {
168    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169        write!(f, "<untyped return value>")
170    }
171}
172
173impl UntypedRetVal {
174    pub(crate) fn new(gp: u64, fp: __m128) -> UntypedRetVal {
175        UntypedRetVal { gp, fp }
176    }
177}
178
179impl From<RegVal> for UntypedRetVal {
180    fn from(reg: RegVal) -> UntypedRetVal {
181        match reg {
182            RegVal::GpReg(r) => UntypedRetVal::new(r, unsafe { _mm_setzero_ps() }),
183            RegVal::FpReg(r) => UntypedRetVal::new(0, r),
184        }
185    }
186}
187
188impl<T: Into<Val>> From<T> for UntypedRetVal {
189    fn from(v: T) -> UntypedRetVal {
190        val_to_reg(&v.into()).into()
191    }
192}
193
194macro_rules! impl_from_fp {
195    ( $ty:ty, $f:ident, $as:ident ) => {
196        impl From<UntypedRetVal> for $ty {
197            fn from(retval: UntypedRetVal) -> $ty {
198                $f(retval.fp)
199            }
200        }
201
202        impl From<&UntypedRetVal> for $ty {
203            fn from(retval: &UntypedRetVal) -> $ty {
204                $f(retval.fp)
205            }
206        }
207
208        impl UntypedRetVal {
209            pub fn $as(&self) -> $ty {
210                $f(self.fp)
211            }
212        }
213    };
214}
215
216impl_from_fp!(f32, __m128_as_f32, as_f32);
217impl_from_fp!(f64, __m128_as_f64, as_f64);
218
219macro_rules! impl_from_gp {
220    ( $ty:ty, $as:ident ) => {
221        impl From<UntypedRetVal> for $ty {
222            fn from(retval: UntypedRetVal) -> $ty {
223                retval.gp as $ty
224            }
225        }
226
227        impl From<&UntypedRetVal> for $ty {
228            fn from(retval: &UntypedRetVal) -> $ty {
229                retval.gp as $ty
230            }
231        }
232
233        impl UntypedRetVal {
234            pub fn $as(&self) -> $ty {
235                self.gp as $ty
236            }
237        }
238    };
239}
240
241impl_from_gp!(u8, as_u8);
242impl_from_gp!(u16, as_u16);
243impl_from_gp!(u32, as_u32);
244impl_from_gp!(u64, as_u64);
245
246impl_from_gp!(i8, as_i8);
247impl_from_gp!(i16, as_i16);
248impl_from_gp!(i32, as_i32);
249impl_from_gp!(i64, as_i64);
250
251impl From<UntypedRetVal> for bool {
252    fn from(retval: UntypedRetVal) -> bool {
253        retval.gp != 0
254    }
255}
256
257impl From<&UntypedRetVal> for bool {
258    fn from(retval: &UntypedRetVal) -> bool {
259        retval.gp != 0
260    }
261}
262
263impl UntypedRetVal {
264    pub fn as_bool(&self) -> bool {
265        self.gp != 0
266    }
267
268    pub fn as_ptr<T>(&self) -> *const T {
269        self.gp as *const T
270    }
271
272    pub fn as_mut<T>(&self) -> *mut T {
273        self.gp as *mut T
274    }
275}
276
277impl Default for UntypedRetVal {
278    fn default() -> UntypedRetVal {
279        let fp = unsafe { _mm_setzero_ps() };
280        UntypedRetVal { fp, gp: 0 }
281    }
282}
283
284pub trait UntypedRetValInternal {
285    fn fp(&self) -> __m128;
286    fn gp(&self) -> u64;
287}
288
289impl UntypedRetValInternal for UntypedRetVal {
290    fn fp(&self) -> __m128 {
291        self.fp
292    }
293
294    fn gp(&self) -> u64 {
295        self.gp
296    }
297}
298
299// Helpers that we might want to put in a utils module someday
300
301/// Interpret the contents of a `__m128` register as an `f32`.
302pub fn __m128_as_f32(v: __m128) -> f32 {
303    let mut out: [f32; 4] = [0.0; 4];
304    unsafe {
305        _mm_storeu_ps(&mut out[0] as *mut f32, v);
306    }
307    out[0]
308}
309
310/// Interpret the contents of a `__m128` register as an `f64`.
311pub fn __m128_as_f64(v: __m128) -> f64 {
312    let mut out: [f64; 2] = [0.0; 2];
313    unsafe {
314        let vd = _mm_castps_pd(v);
315        _mm_storeu_pd(&mut out[0] as *mut f64, vd);
316    }
317    out[0]
318}