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        /// A reference (PHP `&`): a box holding one dynamic value, made by
263        /// [`Inst::NewRef`](crate::Inst::NewRef) or by taking a reference to
264        /// a container slot ([`Inst::DRefIndex`](crate::Inst::DRefIndex),
265        /// [`Inst::DRefProp`](crate::Inst::DRefProp)). It is a cell of `dyn`
266        /// (`cell_get`/`cell_set` read and write it), and a container slot
267        /// holding one is *transparent*: reads and writes of the slot go
268        /// through to the reference's value (`specs/LSB.md` ยง5.16).
269        Reference = 14 => "reference",
270    }
271}
272
273code_enum! {
274    /// The state of a coroutine, as [`Inst::CoroStatus`](crate::Inst::CoroStatus)
275    /// reports it (a `u8` code).
276    ///
277    /// ```text
278    /// Created --resume--> Running --yield--> Yielded  --resume--> Running ...
279    ///                        |    --await--> Awaiting --resume--> Running ...
280    ///                        |    --ret----> Returned
281    ///                        +--- error escapes the body --> Failed
282    /// ```
283    ///
284    /// # Examples
285    ///
286    /// ```
287    /// use bytecode_lang::CoroState;
288    ///
289    /// assert_eq!(CoroState::Awaiting.code(), 3);
290    /// assert_eq!(CoroState::from_code(4), Some(CoroState::Returned));
291    /// assert!(CoroState::Yielded.is_resumable());
292    /// assert!(!CoroState::Failed.is_resumable());
293    /// ```
294    CoroState {
295        /// Made by `coro_new`, never resumed: its body has not started.
296        Created = 0 => "created",
297        /// Executing: it is the running coroutine or one of its resumers.
298        Running = 1 => "running",
299        /// Suspended by `yield`.
300        Yielded = 2 => "yielded",
301        /// Suspended by `await`.
302        Awaiting = 3 => "awaiting",
303        /// Its body returned; it can never run again.
304        Returned = 4 => "returned",
305        /// An error escaped its body; it can never run again.
306        Failed = 5 => "failed",
307    }
308}
309
310impl CoroState {
311    /// Whether `resume`, `resume_throw`, and `coro_close` would run code in
312    /// a coroutine in this state (`Created`, `Yielded`, or `Awaiting`).
313    ///
314    /// # Examples
315    ///
316    /// ```
317    /// use bytecode_lang::CoroState;
318    ///
319    /// assert!(CoroState::Created.is_resumable());
320    /// assert!(!CoroState::Running.is_resumable());
321    /// ```
322    #[must_use]
323    pub const fn is_resumable(self) -> bool {
324        matches!(
325            self,
326            CoroState::Created | CoroState::Yielded | CoroState::Awaiting
327        )
328    }
329}
330
331code_enum! {
332    /// A static representation crossing the `dyn` boundary: the modifier of
333    /// [`Inst::ToDyn`](crate::Inst::ToDyn) and
334    /// [`Inst::FromDyn`](crate::Inst::FromDyn).
335    ///
336    /// # Examples
337    ///
338    /// ```
339    /// use bytecode_lang::Prim;
340    ///
341    /// assert_eq!(Prim::I64.to_string(), "i64");
342    /// assert_eq!(Prim::from_code(13), Some(Prim::Ref));
343    /// ```
344    Prim {
345        /// `bool`.
346        Bool = 0 => "bool",
347        /// `i8`.
348        I8 = 1 => "i8",
349        /// `i16`.
350        I16 = 2 => "i16",
351        /// `i32`.
352        I32 = 3 => "i32",
353        /// `i64`.
354        I64 = 4 => "i64",
355        /// `u8`.
356        U8 = 5 => "u8",
357        /// `u16`.
358        U16 = 6 => "u16",
359        /// `u32`.
360        U32 = 7 => "u32",
361        /// `u64`.
362        U64 = 8 => "u64",
363        /// `f32`.
364        F32 = 9 => "f32",
365        /// `f64`.
366        F64 = 10 => "f64",
367        /// `char`.
368        Char = 11 => "char",
369        /// `str`.
370        Str = 12 => "str",
371        /// Any `ref` type (only `to_dyn`; `from_dyn` to a reference is a
372        /// checked [`Cast`](crate::Inst::Cast)).
373        Ref = 13 => "ref",
374    }
375}
376
377/// An entry of the module's type table.
378///
379/// Types refer to each other by [`TypeId`], so recursive types (a list node
380/// pointing at itself) are plain data and the table never nests.
381///
382/// # Examples
383///
384/// ```
385/// use bytecode_lang::{FuncType, TypeDef, ValType};
386///
387/// let sig = TypeDef::Func(FuncType { params: vec![ValType::I64], results: vec![ValType::I64] });
388/// assert_eq!(sig.to_string(), "func (i64) -> (i64)");
389/// assert_eq!(TypeDef::Array(ValType::Dyn).to_string(), "array dyn");
390/// ```
391#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
392pub enum TypeDef {
393    /// A function signature, used by functions, imports, closures, and
394    /// indirect calls.
395    Func(FuncType),
396    /// A struct (record) type: named fields at fixed slots, named methods,
397    /// and an optional parent for single inheritance.
398    Struct(StructDef),
399    /// A growable array of one element type.
400    Array(ValType),
401    /// An insertion-ordered hash map (the PHP array and the Python dict).
402    Map {
403        /// The key type.
404        key: ValType,
405        /// The value type.
406        value: ValType,
407    },
408    /// A mutable box holding one value: how closures share a mutable
409    /// captured variable.
410    Cell(ValType),
411    /// An iterator yielding keys and values of these types.
412    Iter {
413        /// The key type (`i64` for arrays).
414        key: ValType,
415        /// The value type.
416        value: ValType,
417    },
418    /// A coroutine handle. Values crossing a suspension (yielded, sent,
419    /// returned) are always `dyn`: a coroutine is stackful, so a `yield` in a
420    /// nested call cannot know its coroutine's static type.
421    Coroutine,
422}
423
424impl TypeDef {
425    /// The encoding tag.
426    pub(crate) const fn tag(&self) -> u8 {
427        match self {
428            TypeDef::Func(_) => 0,
429            TypeDef::Struct(_) => 1,
430            TypeDef::Array(_) => 2,
431            TypeDef::Map { .. } => 3,
432            TypeDef::Cell(_) => 4,
433            TypeDef::Iter { .. } => 5,
434            TypeDef::Coroutine => 6,
435        }
436    }
437
438    /// Whether two equal definitions denote the same type. Structs are
439    /// nominal (two identical declarations are two types); everything else
440    /// is structural, so the builder deduplicates it.
441    pub(crate) const fn is_structural(&self) -> bool {
442        !matches!(self, TypeDef::Struct(_))
443    }
444}
445
446/// A function signature.
447///
448/// LSB functions return zero or one value; the verifier (v0.5) rejects more.
449///
450/// # Examples
451///
452/// ```
453/// use bytecode_lang::{FuncType, ValType};
454///
455/// let sig = FuncType { params: vec![ValType::Dyn, ValType::Dyn], results: vec![] };
456/// assert_eq!(sig.to_string(), "func (dyn, dyn) -> ()");
457/// ```
458#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
459pub struct FuncType {
460    /// Parameter types, in order.
461    pub params: Vec<ValType>,
462    /// Result types (zero or one).
463    pub results: Vec<ValType>,
464}
465
466/// A struct (record) type.
467///
468/// Field slots are numbered across the parent chain: a struct's field list
469/// begins with its parent's fields, so a child instance can be used wherever
470/// the parent is expected. Methods are looked up by name by
471/// [`Inst::GetProp`](crate::Inst::GetProp), the struct's own first, then the
472/// parent's.
473///
474/// # Examples
475///
476/// ```
477/// use bytecode_lang::{Field, StrId, StructDef, ValType};
478///
479/// let point = StructDef {
480///     name: StrId(0),
481///     parent: None,
482///     fields: vec![Field { name: StrId(1), ty: ValType::F64 }, Field { name: StrId(2), ty: ValType::F64 }],
483///     methods: vec![],
484/// };
485/// assert_eq!(point.fields.len(), 2);
486/// ```
487#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
488pub struct StructDef {
489    /// The type's name.
490    pub name: StrId,
491    /// The parent struct type, if any.
492    pub parent: Option<TypeId>,
493    /// All fields, the parent's first.
494    pub fields: Vec<Field>,
495    /// Methods declared by this type (not the parent's).
496    pub methods: Vec<Method>,
497}
498
499/// A named, typed field of a [`StructDef`].
500///
501/// # Examples
502///
503/// ```
504/// use bytecode_lang::{Field, StrId, ValType};
505///
506/// let f = Field { name: StrId(4), ty: ValType::Dyn };
507/// assert_eq!(f.ty, ValType::Dyn);
508/// ```
509#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
510pub struct Field {
511    /// The field's name.
512    pub name: StrId,
513    /// The field's type.
514    pub ty: ValType,
515}
516
517/// A named method of a [`StructDef`]: the function receives the instance as
518/// its first parameter.
519///
520/// # Examples
521///
522/// ```
523/// use bytecode_lang::{FuncId, Method, StrId};
524///
525/// let m = Method { name: StrId(7), func: FuncId(2) };
526/// assert_eq!(m.func, FuncId(2));
527/// ```
528#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
529pub struct Method {
530    /// The method's name.
531    pub name: StrId,
532    /// The implementing function.
533    pub func: FuncId,
534}
535
536fn write_list(f: &mut fmt::Formatter<'_>, types: &[ValType]) -> fmt::Result {
537    f.write_str("(")?;
538    for (i, ty) in types.iter().enumerate() {
539        if i > 0 {
540            f.write_str(", ")?;
541        }
542        write!(f, "{ty}")?;
543    }
544    f.write_str(")")
545}
546
547impl fmt::Display for FuncType {
548    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
549        f.write_str("func ")?;
550        write_list(f, &self.params)?;
551        f.write_str(" -> ")?;
552        write_list(f, &self.results)
553    }
554}
555
556impl fmt::Display for StructDef {
557    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
558        write!(f, "struct {}", self.name)?;
559        if let Some(parent) = self.parent {
560            write!(f, " : {parent}")?;
561        }
562        f.write_str(" {")?;
563        for (i, field) in self.fields.iter().enumerate() {
564            let sep = if i == 0 { " " } else { ", " };
565            write!(f, "{sep}#{i} {}: {}", field.name, field.ty)?;
566        }
567        f.write_str(" }")?;
568        if !self.methods.is_empty() {
569            f.write_str(" methods {")?;
570            for (i, method) in self.methods.iter().enumerate() {
571                let sep = if i == 0 { " " } else { ", " };
572                write!(f, "{sep}{} = {}", method.name, method.func)?;
573            }
574            f.write_str(" }")?;
575        }
576        Ok(())
577    }
578}
579
580impl fmt::Display for TypeDef {
581    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
582        match self {
583            TypeDef::Func(sig) => write!(f, "{sig}"),
584            TypeDef::Struct(def) => write!(f, "{def}"),
585            TypeDef::Array(elem) => write!(f, "array {elem}"),
586            TypeDef::Map { key, value } => write!(f, "map {key} -> {value}"),
587            TypeDef::Cell(elem) => write!(f, "cell {elem}"),
588            TypeDef::Iter { key, value } => write!(f, "iter {key} -> {value}"),
589            TypeDef::Coroutine => f.write_str("coroutine"),
590        }
591    }
592}
593
594#[cfg(test)]
595mod tests {
596    use alloc::string::ToString;
597    use alloc::vec;
598
599    use super::*;
600
601    #[test]
602    fn simple_tags_round_trip() {
603        for tag in 0..=13u8 {
604            let ty = ValType::from_simple_tag(tag).map(ValType::tag);
605            assert_eq!(ty, Some(tag));
606        }
607        assert_eq!(ValType::from_simple_tag(14), None);
608        assert_eq!(ValType::Ref(TypeId(9)).tag(), 14);
609    }
610
611    #[test]
612    fn int_value_types_map_back() {
613        for &ty in IntTy::ALL {
614            assert_eq!(ValType::int(ty).as_int(), Some(ty));
615        }
616    }
617
618    #[test]
619    fn struct_display_lists_fields_and_methods() {
620        let def = StructDef {
621            name: StrId(1),
622            parent: Some(TypeId(0)),
623            fields: vec![Field {
624                name: StrId(2),
625                ty: ValType::I64,
626            }],
627            methods: vec![Method {
628                name: StrId(3),
629                func: FuncId(4),
630            }],
631        };
632        assert_eq!(
633            TypeDef::Struct(def).to_string(),
634            "struct s1 : t0 { #0 s2: i64 } methods { s3 = f4 }"
635        );
636    }
637}