Skip to main content

vyre_reference/
value.rs

1//! Runtime values accepted and returned by the core reference interpreter.
2
3use std::sync::Arc;
4
5/// A concrete value passed into or returned from the reference interpreter.
6#[non_exhaustive]
7#[derive(Debug, Clone)]
8pub enum Value {
9    /// Unsigned 32-bit integer.
10    U32(u32),
11    /// Signed 32-bit integer.
12    I32(i32),
13    /// Unsigned 64-bit integer.
14    U64(u64),
15    /// Boolean value.
16    Bool(bool),
17    /// Raw little-endian storage bytes.
18    Bytes(Arc<[u8]>),
19    /// Floating-point value represented with stable host bits.
20    Float(f64),
21    /// Fixed-size array of values.
22    Array(Vec<Value>),
23}
24
25impl PartialEq for Value {
26    fn eq(&self, other: &Self) -> bool {
27        match (self, other) {
28            (Self::U32(a), Self::U32(b)) => a == b,
29            (Self::I32(a), Self::I32(b)) => a == b,
30            (Self::U64(a), Self::U64(b)) => a == b,
31            (Self::Bool(a), Self::Bool(b)) => a == b,
32            (Self::Bytes(a), Self::Bytes(b)) => a == b,
33            (Self::Float(a), Self::Float(b)) => a.to_bits() == b.to_bits(),
34            (Self::Array(a), Self::Array(b)) => a == b,
35            _ => false,
36        }
37    }
38}
39
40impl Eq for Value {}
41
42impl Value {
43    /// Interpret the value using the IR truth convention.
44    #[must_use]
45    pub fn truthy(&self) -> bool {
46        match self {
47            Self::Array(values) => !values.is_empty(),
48            Self::Float(value) => *value != 0.0,
49            _ => self.try_as_u32().unwrap_or(1) != 0,
50        }
51    }
52
53    /// Return this value as little-endian bytes for buffer initialization.
54    #[must_use]
55    pub fn to_bytes(&self) -> Vec<u8> {
56        match self {
57            Self::U32(value) => value.to_le_bytes().to_vec(),
58            Self::I32(value) => value.to_le_bytes().to_vec(),
59            Self::U64(value) => value.to_le_bytes().to_vec(),
60            Self::Bool(value) => u32::from(*value).to_le_bytes().to_vec(),
61            Self::Bytes(bytes) => bytes.to_vec(),
62            Self::Float(value) => value.to_le_bytes().to_vec(),
63            Self::Array(values) => values.iter().flat_map(Self::to_bytes).collect(),
64        }
65    }
66
67    /// Return this value encoded at the declared input width.
68    #[must_use]
69    pub fn to_bytes_width(&self, declared_width: usize) -> Vec<u8> {
70        let mut bytes = self.to_bytes();
71        if declared_width == 0 {
72            return bytes;
73        }
74        bytes.resize(declared_width, 0);
75        bytes.truncate(declared_width);
76        bytes
77    }
78
79    /// Append this value encoded at the declared input width without
80    /// allocating a temporary byte vector for the caller.
81    ///
82    /// # Errors
83    ///
84    /// Returns an error if the destination length would overflow.
85    pub fn extend_bytes_width(
86        &self,
87        declared_width: usize,
88        out: &mut Vec<u8>,
89    ) -> Result<(), vyre::Error> {
90        let start_len = out.len();
91        let fixed_next_len = if declared_width == 0 {
92            None
93        } else {
94            Some(start_len.checked_add(declared_width).ok_or_else(|| {
95                vyre::Error::interp(
96                    "encoded value byte size overflows usize. Fix: reduce the argument count or byte payload size.",
97                )
98            })?)
99        };
100        match self {
101            Self::U32(value) => extend_fixed_width(&value.to_le_bytes(), declared_width, out),
102            Self::I32(value) => extend_fixed_width(&value.to_le_bytes(), declared_width, out),
103            Self::U64(value) => extend_fixed_width(&value.to_le_bytes(), declared_width, out),
104            Self::Bool(value) => {
105                extend_fixed_width(&u32::from(*value).to_le_bytes(), declared_width, out);
106            }
107            Self::Bytes(bytes) => extend_fixed_width(bytes, declared_width, out),
108            Self::Float(value) => extend_fixed_width(&value.to_le_bytes(), declared_width, out),
109            Self::Array(values) => {
110                for value in values {
111                    value.extend_bytes_width(0, out)?;
112                }
113                if let Some(next_len) = fixed_next_len {
114                    out.truncate(start_len + declared_width.min(out.len() - start_len));
115                    out.resize(next_len, 0);
116                }
117            }
118        }
119        if let Some(next_len) = fixed_next_len {
120            debug_assert_eq!(out.len(), next_len);
121        }
122        Ok(())
123    }
124
125    /// Try to interpret the value as the IR's scalar `u32` word.
126    #[must_use]
127    pub fn try_as_u32(&self) -> Option<u32> {
128        match self {
129            Self::U32(value) => Some(*value),
130            Self::I32(value) => u32::try_from(*value).ok(),
131            Self::U64(value) => u32::try_from(*value).ok(),
132            Self::Bool(value) => Some(u32::from(*value)),
133            Self::Bytes(bytes) => (bytes.len() <= 4).then(|| read_u32_prefix(bytes)),
134            Self::Float(value) => Some(*value as u32),
135            Self::Array(_) => None,
136        }
137    }
138
139    /// Interpret the value as the IR's scalar `u32` word.
140    #[must_use]
141    pub fn as_u32(&self) -> u32 {
142        self.try_as_u32().unwrap_or(0)
143    }
144
145    /// Try to interpret the value as a full `u64`.
146    #[must_use]
147    pub fn try_as_u64(&self) -> Option<u64> {
148        match self {
149            Self::U32(value) => Some(u64::from(*value)),
150            Self::I32(value) => u64::try_from(*value).ok(),
151            Self::U64(value) => Some(*value),
152            Self::Bool(value) => Some(u64::from(*value)),
153            Self::Bytes(bytes) => (bytes.len() <= 8).then(|| read_u64_prefix(bytes)),
154            Self::Float(value) => Some(*value as u64),
155            Self::Array(_) => None,
156        }
157    }
158
159    /// Interpret the value as a full `u64`.
160    #[must_use]
161    pub fn as_u64(&self) -> u64 {
162        self.try_as_u64().unwrap_or(0)
163    }
164
165    /// Try to interpret the value as an `f32`.
166    #[must_use]
167    pub fn try_as_f32(&self) -> Option<f32> {
168        match self {
169            Self::Float(value) => Some(*value as f32),
170            Self::U32(value) => Some(f32::from_bits(*value)),
171            _ => None,
172        }
173    }
174
175    /// Return the full value payload as little-endian bytes.
176    #[must_use]
177    pub fn wide_bytes(&self) -> Vec<u8> {
178        self.to_bytes()
179    }
180
181    /// Create a zero value for the given data type.
182    #[must_use]
183    pub fn zero_for(ty: vyre::ir::DataType) -> Self {
184        Self::try_zero_for(ty).unwrap_or_else(|| Self::Bytes(Arc::from([])))
185    }
186
187    /// Try to create a zero value for the given data type.
188    #[must_use]
189    pub fn try_zero_for(ty: vyre::ir::DataType) -> Option<Self> {
190        match ty {
191            vyre::ir::DataType::U32 => Some(Self::U32(0)),
192            vyre::ir::DataType::I32 => Some(Self::I32(0)),
193            vyre::ir::DataType::U64 => Some(Self::U64(0)),
194            vyre::ir::DataType::Bool => Some(Self::Bool(false)),
195            vyre::ir::DataType::Bytes => Some(Self::Bytes(Arc::from([]))),
196            vyre::ir::DataType::F32 => Some(Self::Float(0.0)),
197            vyre::ir::DataType::Vec2U32 => Some(Self::Bytes(Arc::from(vec![0; 8]))),
198            vyre::ir::DataType::Vec4U32 => Some(Self::Bytes(Arc::from(vec![0; 16]))),
199            _ => None,
200        }
201    }
202
203    /// Create a value from element bytes for the given data type.
204    ///
205    /// # Errors
206    ///
207    /// Returns an error if the byte slice is too short for the declared type.
208    pub fn from_element_bytes(ty: vyre::ir::DataType, bytes: &[u8]) -> Result<Self, String> {
209        match ty {
210            vyre::ir::DataType::U32 => {
211                if bytes.len() < 4 {
212                    return Err("u32 requires 4 bytes".to_string());
213                }
214                Ok(Self::U32(u32::from_le_bytes([
215                    bytes[0], bytes[1], bytes[2], bytes[3],
216                ])))
217            }
218            vyre::ir::DataType::I32 => {
219                if bytes.len() < 4 {
220                    return Err("i32 requires 4 bytes".to_string());
221                }
222                Ok(Self::I32(i32::from_le_bytes([
223                    bytes[0], bytes[1], bytes[2], bytes[3],
224                ])))
225            }
226            vyre::ir::DataType::U64 => {
227                if bytes.len() < 8 {
228                    return Err("u64 requires 8 bytes".to_string());
229                }
230                Ok(Self::U64(u64::from_le_bytes([
231                    bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
232                ])))
233            }
234            vyre::ir::DataType::Bool => {
235                if bytes.len() < 4 {
236                    return Err("bool requires 4 bytes".to_string());
237                }
238                Ok(Self::Bool(
239                    u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) != 0,
240                ))
241            }
242            vyre::ir::DataType::Vec2U32 => {
243                if bytes.len() < 8 {
244                    return Err("vec2u32 requires 8 bytes".to_string());
245                }
246                Ok(Self::Bytes(Arc::from(&bytes[..8])))
247            }
248            vyre::ir::DataType::Vec4U32 => {
249                if bytes.len() < 16 {
250                    return Err("vec4u32 requires 16 bytes".to_string());
251                }
252                Ok(Self::Bytes(Arc::from(&bytes[..16])))
253            }
254            vyre::ir::DataType::F32 => {
255                if bytes.len() < 4 {
256                    return Err("f32 requires 4 bytes".to_string());
257                }
258                Ok(Self::Float(f64::from(f32::from_le_bytes([
259                    bytes[0], bytes[1], bytes[2], bytes[3],
260                ]))))
261            }
262            vyre::ir::DataType::Bytes => Ok(Self::Bytes(Arc::from(bytes))),
263            _ => Ok(Self::Bytes(Arc::from(bytes))),
264        }
265    }
266}
267
268fn extend_fixed_width(bytes: &[u8], declared_width: usize, out: &mut Vec<u8>) {
269    if declared_width == 0 {
270        out.extend_from_slice(bytes);
271        return;
272    }
273    let copied = bytes.len().min(declared_width);
274    out.extend_from_slice(&bytes[..copied]);
275    out.resize(out.len() + (declared_width - copied), 0);
276}
277
278impl From<Vec<u8>> for Value {
279    fn from(bytes: Vec<u8>) -> Self {
280        Self::Bytes(Arc::from(bytes))
281    }
282}
283
284impl From<&[u8]> for Value {
285    fn from(bytes: &[u8]) -> Self {
286        Self::Bytes(Arc::from(bytes))
287    }
288}
289
290fn read_u32_prefix(bytes: &[u8]) -> u32 {
291    let mut padded = [0u8; 4];
292    let len = bytes.len().min(4);
293    padded[..len].copy_from_slice(&bytes[..len]);
294    u32::from_le_bytes(padded)
295}
296
297fn read_u64_prefix(bytes: &[u8]) -> u64 {
298    let mut padded = [0u8; 8];
299    let len = bytes.len().min(8);
300    padded[..len].copy_from_slice(&bytes[..len]);
301    u64::from_le_bytes(padded)
302}
303
304#[cfg(test)]
305mod tests {
306    use super::*;
307    use proptest::prelude::*;
308
309    #[test]
310    fn neg_zero_truthiness_is_false() {
311        assert!(!Value::Float(-0.0).truthy());
312    }
313
314    #[test]
315    fn pos_zero_truthiness_is_false() {
316        assert!(!Value::Float(0.0).truthy());
317    }
318
319    #[test]
320    fn nonzero_float_truthiness_is_true() {
321        assert!(Value::Float(1.0).truthy());
322        assert!(Value::Float(-1.0).truthy());
323        assert!(Value::Float(f64::INFINITY).truthy());
324        assert!(Value::Float(f64::NEG_INFINITY).truthy());
325    }
326
327    proptest! {
328        #[test]
329        fn neg_zero_select_branches_to_false(
330            positive_sign in proptest::bool::ANY,
331        ) {
332            let zero = if positive_sign { 0.0_f64 } else { -0.0_f64 };
333            prop_assert!(!Value::Float(zero).truthy(),
334                "Value::Float({zero}).truthy() must be false to match backend bool(0.0)/bool(-0.0) semantics");
335        }
336    }
337}