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}