Skip to main content

bytecode_lang/
types.rs

1//! Register types, dynamic kinds, and the module's type table.
2
3use alloc::vec::Vec;
4use core::fmt;
5
6use crate::ids::{FuncId, StrId, TypeId};
7use crate::policy::{FloatTy, IntTy};
8
9/// The declared type of a register, a global, a field, or a parameter.
10///
11/// Every register slot is 64 bits wide. Scalars (`bool`, the integers, the
12/// floats, `char`) are stored unboxed in typed registers. [`Dyn`](ValType::Dyn)
13/// holds a dynamically typed value (the NaN-boxed value of the dynamic
14/// languages). [`Str`](ValType::Str), [`Ref`](ValType::Ref), and `Dyn`
15/// holding a heap object share **one** reference representation, and `nil`
16/// is the null reference, so a heap instruction works the same whichever of
17/// those registers holds the object. Declared types are what make a frame's
18/// garbage-collection roots exact: a register is traced if and only if its
19/// type [`is_reference`](ValType::is_reference).
20///
21/// # Examples
22///
23/// ```
24/// use bytecode_lang::{IntTy, TypeId, ValType};
25///
26/// assert_eq!(ValType::int(IntTy::U8), ValType::U8);
27/// assert_eq!(ValType::I64.as_int(), Some(IntTy::I64));
28/// assert!(ValType::Dyn.is_reference());
29/// assert!(!ValType::F64.is_reference());
30/// assert_eq!(ValType::Ref(TypeId(3)).to_string(), "ref t3");
31/// ```
32#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
33pub enum ValType {
34    /// `false` or `true`.
35    Bool,
36    /// Signed 8-bit integer.
37    I8,
38    /// Signed 16-bit integer.
39    I16,
40    /// Signed 32-bit integer.
41    I32,
42    /// Signed 64-bit integer.
43    I64,
44    /// Unsigned 8-bit integer.
45    U8,
46    /// Unsigned 16-bit integer.
47    U16,
48    /// Unsigned 32-bit integer.
49    U32,
50    /// Unsigned 64-bit integer.
51    U64,
52    /// IEEE 754 binary32.
53    F32,
54    /// IEEE 754 binary64.
55    F64,
56    /// A Unicode scalar value.
57    Char,
58    /// An immutable byte string, or `nil`.
59    Str,
60    /// A dynamically typed value of any [`Kind`].
61    Dyn,
62    /// A reference to a heap object of the given type-table entry, or `nil`.
63    Ref(TypeId),
64}
65
66impl ValType {
67    /// The integer value type for `ty`.
68    ///
69    /// # Examples
70    ///
71    /// ```
72    /// use bytecode_lang::{IntTy, ValType};
73    ///
74    /// assert_eq!(ValType::int(IntTy::I32), ValType::I32);
75    /// ```
76    #[must_use]
77    pub const fn int(ty: IntTy) -> Self {
78        match ty {
79            IntTy::I8 => ValType::I8,
80            IntTy::I16 => ValType::I16,
81            IntTy::I32 => ValType::I32,
82            IntTy::I64 => ValType::I64,
83            IntTy::U8 => ValType::U8,
84            IntTy::U16 => ValType::U16,
85            IntTy::U32 => ValType::U32,
86            IntTy::U64 => ValType::U64,
87        }
88    }
89
90    /// The float value type for `ty`.
91    ///
92    /// # Examples
93    ///
94    /// ```
95    /// use bytecode_lang::{FloatTy, ValType};
96    ///
97    /// assert_eq!(ValType::float(FloatTy::F32), ValType::F32);
98    /// ```
99    #[must_use]
100    pub const fn float(ty: FloatTy) -> Self {
101        match ty {
102            FloatTy::F32 => ValType::F32,
103            FloatTy::F64 => ValType::F64,
104        }
105    }
106
107    /// The integer type, if this is an integer value type.
108    ///
109    /// # Examples
110    ///
111    /// ```
112    /// use bytecode_lang::{IntTy, ValType};
113    ///
114    /// assert_eq!(ValType::U64.as_int(), Some(IntTy::U64));
115    /// assert_eq!(ValType::F64.as_int(), None);
116    /// ```
117    #[must_use]
118    pub const fn as_int(self) -> Option<IntTy> {
119        match self {
120            ValType::I8 => Some(IntTy::I8),
121            ValType::I16 => Some(IntTy::I16),
122            ValType::I32 => Some(IntTy::I32),
123            ValType::I64 => Some(IntTy::I64),
124            ValType::U8 => Some(IntTy::U8),
125            ValType::U16 => Some(IntTy::U16),
126            ValType::U32 => Some(IntTy::U32),
127            ValType::U64 => Some(IntTy::U64),
128            _ => None,
129        }
130    }
131
132    /// Whether a register of this type can hold a heap reference, and so is
133    /// a garbage-collection root.
134    ///
135    /// # Examples
136    ///
137    /// ```
138    /// use bytecode_lang::{TypeId, ValType};
139    ///
140    /// assert!(ValType::Str.is_reference());
141    /// assert!(ValType::Ref(TypeId(0)).is_reference());
142    /// assert!(!ValType::Bool.is_reference());
143    /// ```
144    #[must_use]
145    pub const fn is_reference(self) -> bool {
146        matches!(self, ValType::Str | ValType::Dyn | ValType::Ref(_))
147    }
148
149    /// The encoding tag (0..=14).
150    pub(crate) const fn tag(self) -> u8 {
151        match self {
152            ValType::Bool => 0,
153            ValType::I8 => 1,
154            ValType::I16 => 2,
155            ValType::I32 => 3,
156            ValType::I64 => 4,
157            ValType::U8 => 5,
158            ValType::U16 => 6,
159            ValType::U32 => 7,
160            ValType::U64 => 8,
161            ValType::F32 => 9,
162            ValType::F64 => 10,
163            ValType::Char => 11,
164            ValType::Str => 12,
165            ValType::Dyn => 13,
166            ValType::Ref(_) => 14,
167        }
168    }
169
170    /// The type for a tag that carries no index, or `None` (tag 14 needs
171    /// its type index read separately).
172    pub(crate) const fn from_simple_tag(tag: u8) -> Option<ValType> {
173        Some(match tag {
174            0 => ValType::Bool,
175            1 => ValType::I8,
176            2 => ValType::I16,
177            3 => ValType::I32,
178            4 => ValType::I64,
179            5 => ValType::U8,
180            6 => ValType::U16,
181            7 => ValType::U32,
182            8 => ValType::U64,
183            9 => ValType::F32,
184            10 => ValType::F64,
185            11 => ValType::Char,
186            12 => ValType::Str,
187            13 => ValType::Dyn,
188            _ => return None,
189        })
190    }
191}
192
193impl fmt::Display for ValType {
194    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195        let name = match self {
196            ValType::Bool => "bool",
197            ValType::I8 => "i8",
198            ValType::I16 => "i16",
199            ValType::I32 => "i32",
200            ValType::I64 => "i64",
201            ValType::U8 => "u8",
202            ValType::U16 => "u16",
203            ValType::U32 => "u32",
204            ValType::U64 => "u64",
205            ValType::F32 => "f32",
206            ValType::F64 => "f64",
207            ValType::Char => "char",
208            ValType::Str => "str",
209            ValType::Dyn => "dyn",
210            ValType::Ref(ty) => return write!(f, "ref {ty}"),
211        };
212        f.write_str(name)
213    }
214}
215
216code_enum! {
217    /// The kind of a dynamic value: what [`Inst::TypeOf`](crate::Inst::TypeOf)
218    /// returns and [`Inst::IsKind`](crate::Inst::IsKind) tests.
219    ///
220    /// Dynamic integers are 64-bit signed and dynamic floats are binary64,
221    /// whatever their boxed representation.
222    ///
223    /// # Examples
224    ///
225    /// ```
226    /// use bytecode_lang::Kind;
227    ///
228    /// assert_eq!(Kind::Map.code(), 7);
229    /// assert_eq!(Kind::from_code(5), Some(Kind::Str));
230    /// assert_eq!(Kind::Object.to_string(), "object");
231    /// ```
232    Kind {
233        /// `nil`: the absent value, and the null reference.
234        Nil = 0 => "nil",
235        /// A boolean.
236        Bool = 1 => "bool",
237        /// A 64-bit signed integer.
238        Int = 2 => "int",
239        /// A binary64 float.
240        Float = 3 => "float",
241        /// A Unicode scalar value.
242        Char = 4 => "char",
243        /// An immutable byte string.
244        Str = 5 => "str",
245        /// An array.
246        Array = 6 => "array",
247        /// An ordered hash map.
248        Map = 7 => "map",
249        /// A struct instance.
250        Object = 8 => "object",
251        /// A callable: a closure, a function, or a host function.
252        Function = 9 => "function",
253        /// A mutable cell.
254        Cell = 10 => "cell",
255        /// An iterator.
256        Iter = 11 => "iter",
257        /// A runtime error value (see [`Inst::ErrCode`](crate::Inst::ErrCode)).
258        Error = 12 => "error",
259        /// A coroutine: a suspendable stack of frames (generators, async
260        /// tasks; see [`Inst::CoroNew`](crate::Inst::CoroNew)).
261        Coroutine = 13 => "coroutine",
262    }
263}
264
265code_enum! {
266    /// The state of a coroutine, as [`Inst::CoroStatus`](crate::Inst::CoroStatus)
267    /// reports it (a `u8` code).
268    ///
269    /// ```text
270    /// Created --resume--> Running --yield--> Yielded  --resume--> Running ...
271    ///                        |    --await--> Awaiting --resume--> Running ...
272    ///                        |    --ret----> Returned
273    ///                        +--- error escapes the body --> Failed
274    /// ```
275    ///
276    /// # Examples
277    ///
278    /// ```
279    /// use bytecode_lang::CoroState;
280    ///
281    /// assert_eq!(CoroState::Awaiting.code(), 3);
282    /// assert_eq!(CoroState::from_code(4), Some(CoroState::Returned));
283    /// assert!(CoroState::Yielded.is_resumable());
284    /// assert!(!CoroState::Failed.is_resumable());
285    /// ```
286    CoroState {
287        /// Made by `coro_new`, never resumed: its body has not started.
288        Created = 0 => "created",
289        /// Executing: it is the running coroutine or one of its resumers.
290        Running = 1 => "running",
291        /// Suspended by `yield`.
292        Yielded = 2 => "yielded",
293        /// Suspended by `await`.
294        Awaiting = 3 => "awaiting",
295        /// Its body returned; it can never run again.
296        Returned = 4 => "returned",
297        /// An error escaped its body; it can never run again.
298        Failed = 5 => "failed",
299    }
300}
301
302impl CoroState {
303    /// Whether `resume`, `resume_throw`, and `coro_close` would run code in
304    /// a coroutine in this state (`Created`, `Yielded`, or `Awaiting`).
305    ///
306    /// # Examples
307    ///
308    /// ```
309    /// use bytecode_lang::CoroState;
310    ///
311    /// assert!(CoroState::Created.is_resumable());
312    /// assert!(!CoroState::Running.is_resumable());
313    /// ```
314    #[must_use]
315    pub const fn is_resumable(self) -> bool {
316        matches!(
317            self,
318            CoroState::Created | CoroState::Yielded | CoroState::Awaiting
319        )
320    }
321}
322
323code_enum! {
324    /// A static representation crossing the `dyn` boundary: the modifier of
325    /// [`Inst::ToDyn`](crate::Inst::ToDyn) and
326    /// [`Inst::FromDyn`](crate::Inst::FromDyn).
327    ///
328    /// # Examples
329    ///
330    /// ```
331    /// use bytecode_lang::Prim;
332    ///
333    /// assert_eq!(Prim::I64.to_string(), "i64");
334    /// assert_eq!(Prim::from_code(13), Some(Prim::Ref));
335    /// ```
336    Prim {
337        /// `bool`.
338        Bool = 0 => "bool",
339        /// `i8`.
340        I8 = 1 => "i8",
341        /// `i16`.
342        I16 = 2 => "i16",
343        /// `i32`.
344        I32 = 3 => "i32",
345        /// `i64`.
346        I64 = 4 => "i64",
347        /// `u8`.
348        U8 = 5 => "u8",
349        /// `u16`.
350        U16 = 6 => "u16",
351        /// `u32`.
352        U32 = 7 => "u32",
353        /// `u64`.
354        U64 = 8 => "u64",
355        /// `f32`.
356        F32 = 9 => "f32",
357        /// `f64`.
358        F64 = 10 => "f64",
359        /// `char`.
360        Char = 11 => "char",
361        /// `str`.
362        Str = 12 => "str",
363        /// Any `ref` type (only `to_dyn`; `from_dyn` to a reference is a
364        /// checked [`Cast`](crate::Inst::Cast)).
365        Ref = 13 => "ref",
366    }
367}
368
369/// An entry of the module's type table.
370///
371/// Types refer to each other by [`TypeId`], so recursive types (a list node
372/// pointing at itself) are plain data and the table never nests.
373///
374/// # Examples
375///
376/// ```
377/// use bytecode_lang::{FuncType, TypeDef, ValType};
378///
379/// let sig = TypeDef::Func(FuncType { params: vec![ValType::I64], results: vec![ValType::I64] });
380/// assert_eq!(sig.to_string(), "func (i64) -> (i64)");
381/// assert_eq!(TypeDef::Array(ValType::Dyn).to_string(), "array dyn");
382/// ```
383#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
384pub enum TypeDef {
385    /// A function signature, used by functions, imports, closures, and
386    /// indirect calls.
387    Func(FuncType),
388    /// A struct (record) type: named fields at fixed slots, named methods,
389    /// and an optional parent for single inheritance.
390    Struct(StructDef),
391    /// A growable array of one element type.
392    Array(ValType),
393    /// An insertion-ordered hash map (the PHP array and the Python dict).
394    Map {
395        /// The key type.
396        key: ValType,
397        /// The value type.
398        value: ValType,
399    },
400    /// A mutable box holding one value: how closures share a mutable
401    /// captured variable.
402    Cell(ValType),
403    /// An iterator yielding keys and values of these types.
404    Iter {
405        /// The key type (`i64` for arrays).
406        key: ValType,
407        /// The value type.
408        value: ValType,
409    },
410    /// A coroutine handle. Values crossing a suspension (yielded, sent,
411    /// returned) are always `dyn`: a coroutine is stackful, so a `yield` in a
412    /// nested call cannot know its coroutine's static type.
413    Coroutine,
414}
415
416impl TypeDef {
417    /// The encoding tag.
418    pub(crate) const fn tag(&self) -> u8 {
419        match self {
420            TypeDef::Func(_) => 0,
421            TypeDef::Struct(_) => 1,
422            TypeDef::Array(_) => 2,
423            TypeDef::Map { .. } => 3,
424            TypeDef::Cell(_) => 4,
425            TypeDef::Iter { .. } => 5,
426            TypeDef::Coroutine => 6,
427        }
428    }
429
430    /// Whether two equal definitions denote the same type. Structs are
431    /// nominal (two identical declarations are two types); everything else
432    /// is structural, so the builder deduplicates it.
433    pub(crate) const fn is_structural(&self) -> bool {
434        !matches!(self, TypeDef::Struct(_))
435    }
436}
437
438/// A function signature.
439///
440/// LSB functions return zero or one value; the verifier (v0.5) rejects more.
441///
442/// # Examples
443///
444/// ```
445/// use bytecode_lang::{FuncType, ValType};
446///
447/// let sig = FuncType { params: vec![ValType::Dyn, ValType::Dyn], results: vec![] };
448/// assert_eq!(sig.to_string(), "func (dyn, dyn) -> ()");
449/// ```
450#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
451pub struct FuncType {
452    /// Parameter types, in order.
453    pub params: Vec<ValType>,
454    /// Result types (zero or one).
455    pub results: Vec<ValType>,
456}
457
458/// A struct (record) type.
459///
460/// Field slots are numbered across the parent chain: a struct's field list
461/// begins with its parent's fields, so a child instance can be used wherever
462/// the parent is expected. Methods are looked up by name by
463/// [`Inst::GetProp`](crate::Inst::GetProp), the struct's own first, then the
464/// parent's.
465///
466/// # Examples
467///
468/// ```
469/// use bytecode_lang::{Field, StrId, StructDef, ValType};
470///
471/// let point = StructDef {
472///     name: StrId(0),
473///     parent: None,
474///     fields: vec![Field { name: StrId(1), ty: ValType::F64 }, Field { name: StrId(2), ty: ValType::F64 }],
475///     methods: vec![],
476/// };
477/// assert_eq!(point.fields.len(), 2);
478/// ```
479#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
480pub struct StructDef {
481    /// The type's name.
482    pub name: StrId,
483    /// The parent struct type, if any.
484    pub parent: Option<TypeId>,
485    /// All fields, the parent's first.
486    pub fields: Vec<Field>,
487    /// Methods declared by this type (not the parent's).
488    pub methods: Vec<Method>,
489}
490
491/// A named, typed field of a [`StructDef`].
492///
493/// # Examples
494///
495/// ```
496/// use bytecode_lang::{Field, StrId, ValType};
497///
498/// let f = Field { name: StrId(4), ty: ValType::Dyn };
499/// assert_eq!(f.ty, ValType::Dyn);
500/// ```
501#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
502pub struct Field {
503    /// The field's name.
504    pub name: StrId,
505    /// The field's type.
506    pub ty: ValType,
507}
508
509/// A named method of a [`StructDef`]: the function receives the instance as
510/// its first parameter.
511///
512/// # Examples
513///
514/// ```
515/// use bytecode_lang::{FuncId, Method, StrId};
516///
517/// let m = Method { name: StrId(7), func: FuncId(2) };
518/// assert_eq!(m.func, FuncId(2));
519/// ```
520#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
521pub struct Method {
522    /// The method's name.
523    pub name: StrId,
524    /// The implementing function.
525    pub func: FuncId,
526}
527
528fn write_list(f: &mut fmt::Formatter<'_>, types: &[ValType]) -> fmt::Result {
529    f.write_str("(")?;
530    for (i, ty) in types.iter().enumerate() {
531        if i > 0 {
532            f.write_str(", ")?;
533        }
534        write!(f, "{ty}")?;
535    }
536    f.write_str(")")
537}
538
539impl fmt::Display for FuncType {
540    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
541        f.write_str("func ")?;
542        write_list(f, &self.params)?;
543        f.write_str(" -> ")?;
544        write_list(f, &self.results)
545    }
546}
547
548impl fmt::Display for StructDef {
549    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
550        write!(f, "struct {}", self.name)?;
551        if let Some(parent) = self.parent {
552            write!(f, " : {parent}")?;
553        }
554        f.write_str(" {")?;
555        for (i, field) in self.fields.iter().enumerate() {
556            let sep = if i == 0 { " " } else { ", " };
557            write!(f, "{sep}#{i} {}: {}", field.name, field.ty)?;
558        }
559        f.write_str(" }")?;
560        if !self.methods.is_empty() {
561            f.write_str(" methods {")?;
562            for (i, method) in self.methods.iter().enumerate() {
563                let sep = if i == 0 { " " } else { ", " };
564                write!(f, "{sep}{} = {}", method.name, method.func)?;
565            }
566            f.write_str(" }")?;
567        }
568        Ok(())
569    }
570}
571
572impl fmt::Display for TypeDef {
573    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
574        match self {
575            TypeDef::Func(sig) => write!(f, "{sig}"),
576            TypeDef::Struct(def) => write!(f, "{def}"),
577            TypeDef::Array(elem) => write!(f, "array {elem}"),
578            TypeDef::Map { key, value } => write!(f, "map {key} -> {value}"),
579            TypeDef::Cell(elem) => write!(f, "cell {elem}"),
580            TypeDef::Iter { key, value } => write!(f, "iter {key} -> {value}"),
581            TypeDef::Coroutine => f.write_str("coroutine"),
582        }
583    }
584}
585
586#[cfg(test)]
587mod tests {
588    use alloc::string::ToString;
589    use alloc::vec;
590
591    use super::*;
592
593    #[test]
594    fn simple_tags_round_trip() {
595        for tag in 0..=13u8 {
596            let ty = ValType::from_simple_tag(tag).map(ValType::tag);
597            assert_eq!(ty, Some(tag));
598        }
599        assert_eq!(ValType::from_simple_tag(14), None);
600        assert_eq!(ValType::Ref(TypeId(9)).tag(), 14);
601    }
602
603    #[test]
604    fn int_value_types_map_back() {
605        for &ty in IntTy::ALL {
606            assert_eq!(ValType::int(ty).as_int(), Some(ty));
607        }
608    }
609
610    #[test]
611    fn struct_display_lists_fields_and_methods() {
612        let def = StructDef {
613            name: StrId(1),
614            parent: Some(TypeId(0)),
615            fields: vec![Field {
616                name: StrId(2),
617                ty: ValType::I64,
618            }],
619            methods: vec![Method {
620                name: StrId(3),
621                func: FuncId(4),
622            }],
623        };
624        assert_eq!(
625            TypeDef::Struct(def).to_string(),
626            "struct s1 : t0 { #0 s2: i64 } methods { s3 = f4 }"
627        );
628    }
629}