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}