use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
use crate::ids::{ConstId, FuncId, GlobalId, ImportId, Reg, StrId, Target, TypeId};
use crate::inst::Inst;
use crate::types::{TypeDef, ValType};
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum Const {
Bool(bool),
Int(i64),
UInt(u64),
F32(u32),
F64(u64),
Char(char),
Str(StrId),
Bytes(Vec<u8>),
Array(Vec<ConstId>),
Map(Vec<(ConstId, ConstId)>),
}
impl Const {
#[must_use]
pub fn f32(value: f32) -> Self {
Const::F32(value.to_bits())
}
#[must_use]
pub fn f64(value: f64) -> Self {
Const::F64(value.to_bits())
}
pub(crate) const fn tag(&self) -> u8 {
match self {
Const::Bool(_) => 0,
Const::Int(_) => 1,
Const::UInt(_) => 2,
Const::F32(_) => 3,
Const::F64(_) => 4,
Const::Char(_) => 5,
Const::Str(_) => 6,
Const::Bytes(_) => 7,
Const::Array(_) => 8,
Const::Map(_) => 9,
}
}
pub(crate) fn children(&self) -> impl Iterator<Item = ConstId> + '_ {
let (list, pairs): (&[ConstId], &[(ConstId, ConstId)]) = match self {
Const::Array(items) => (items, &[]),
Const::Map(entries) => (&[], entries),
_ => (&[], &[]),
};
list.iter()
.copied()
.chain(pairs.iter().flat_map(|&(k, v)| [k, v]))
}
}
impl fmt::Display for Const {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Const::Bool(v) => write!(f, "bool {v}"),
Const::Int(v) => write!(f, "int {v}"),
Const::UInt(v) => write!(f, "uint {v}"),
Const::F32(bits) => {
let v = f32::from_bits(*bits);
if v.is_nan() {
write!(f, "f32 nan:{bits:#010x}")
} else {
write!(f, "f32 {v:?}")
}
}
Const::F64(bits) => {
let v = f64::from_bits(*bits);
if v.is_nan() {
write!(f, "f64 nan:{bits:#018x}")
} else {
write!(f, "f64 {v:?}")
}
}
Const::Char(c) => write!(f, "char {c:?}"),
Const::Str(id) => write!(f, "str {id}"),
Const::Bytes(bytes) => {
f.write_str("bytes b\"")?;
for &b in bytes {
write!(f, "{}", core::ascii::escape_default(b))?;
}
f.write_str("\"")
}
Const::Array(items) => {
f.write_str("array [")?;
for (i, id) in items.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{id}")?;
}
f.write_str("]")
}
Const::Map(entries) => {
f.write_str("map {")?;
for (i, (k, v)) in entries.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{k}: {v}")?;
}
f.write_str("}")
}
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct Import {
pub module: StrId,
pub name: StrId,
pub sig: TypeId,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct Global {
pub name: StrId,
pub ty: ValType,
pub mutable: bool,
pub init: Option<ConstId>,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum ExportItem {
Func(FuncId),
Global(GlobalId),
Type(TypeId),
}
impl ExportItem {
pub(crate) const fn tag(self) -> u8 {
match self {
ExportItem::Func(_) => 0,
ExportItem::Global(_) => 1,
ExportItem::Type(_) => 2,
}
}
pub(crate) const fn index(self) -> u32 {
match self {
ExportItem::Func(id) => id.0,
ExportItem::Global(id) => id.0,
ExportItem::Type(id) => id.0,
}
}
}
impl fmt::Display for ExportItem {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ExportItem::Func(id) => write!(f, "{id}"),
ExportItem::Global(id) => write!(f, "{id}"),
ExportItem::Type(id) => write!(f, "{id}"),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct Export {
pub name: StrId,
pub item: ExportItem,
}
code_enum! {
Hook {
Add = 0 => "add",
Sub = 1 => "sub",
Mul = 2 => "mul",
Div = 3 => "div",
Rem = 4 => "rem",
FloorDiv = 5 => "floor_div",
FloorMod = 6 => "floor_mod",
BitAnd = 7 => "bit_and",
BitOr = 8 => "bit_or",
BitXor = 9 => "bit_xor",
Shl = 10 => "shl",
Shr = 11 => "shr",
Neg = 12 => "neg",
BitNot = 13 => "bit_not",
Eq = 14 => "eq",
Lt = 15 => "lt",
Le = 16 => "le",
Truthy = 17 => "truthy",
Concat = 18 => "concat",
GetIndex = 19 => "get_index",
SetIndex = 20 => "set_index",
GetProp = 21 => "get_prop",
SetProp = 22 => "set_prop",
HasProp = 23 => "has_prop",
Iter = 24 => "iter",
Len = 25 => "len",
Call = 26 => "call",
Spawn = 27 => "spawn",
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum Callee {
Func(FuncId),
Import(ImportId),
}
impl fmt::Display for Callee {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Callee::Func(id) => write!(f, "{id}"),
Callee::Import(id) => write!(f, "{id}"),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct HookBinding {
pub hook: Hook,
pub callee: Callee,
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
pub struct JumpTable {
pub targets: Vec<Target>,
pub default: Target,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct Handler {
pub start: u32,
pub end: u32,
pub target: Target,
pub catch: Reg,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct LineRow {
pub pc: u32,
pub file: StrId,
pub line: u32,
pub column: u32,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct LocalVar {
pub reg: Reg,
pub name: StrId,
pub start: u32,
pub end: u32,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Function {
pub(crate) name: StrId,
pub(crate) sig: TypeId,
pub(crate) regs: Vec<ValType>,
pub(crate) captures: Vec<ValType>,
pub(crate) names: Vec<StrId>,
pub(crate) type_refs: Vec<TypeId>,
pub(crate) tables: Vec<JumpTable>,
pub(crate) handlers: Vec<Handler>,
pub(crate) code: Vec<Inst>,
pub(crate) lines: Vec<LineRow>,
pub(crate) locals: Vec<LocalVar>,
}
impl Function {
#[must_use]
pub fn name(&self) -> StrId {
self.name
}
#[must_use]
pub fn sig(&self) -> TypeId {
self.sig
}
#[must_use]
pub fn regs(&self) -> &[ValType] {
&self.regs
}
#[must_use]
pub fn captures(&self) -> &[ValType] {
&self.captures
}
#[must_use]
pub fn names(&self) -> &[StrId] {
&self.names
}
#[must_use]
pub fn type_refs(&self) -> &[TypeId] {
&self.type_refs
}
#[must_use]
pub fn tables(&self) -> &[JumpTable] {
&self.tables
}
#[must_use]
pub fn handlers(&self) -> &[Handler] {
&self.handlers
}
#[must_use]
pub fn code(&self) -> &[Inst] {
&self.code
}
#[must_use]
pub fn lines(&self) -> &[LineRow] {
&self.lines
}
#[must_use]
pub fn locals(&self) -> &[LocalVar] {
&self.locals
}
}
#[derive(Clone, PartialEq, Eq, Debug, Default)]
pub(crate) struct Strings {
data: String,
ends: Vec<u32>,
}
impl Strings {
pub(crate) fn with_capacity(count: usize, bytes: usize) -> Self {
Strings {
data: String::with_capacity(bytes),
ends: Vec::with_capacity(count),
}
}
pub(crate) fn len(&self) -> usize {
self.ends.len()
}
pub(crate) fn push(&mut self, s: &str) -> bool {
let Some(end) = self
.data
.len()
.checked_add(s.len())
.and_then(|end| u32::try_from(end).ok())
else {
return false;
};
self.data.push_str(s);
self.ends.push(end);
true
}
pub(crate) fn get(&self, index: usize) -> Option<&str> {
let end = *self.ends.get(index)? as usize;
let start = match index.checked_sub(1) {
Some(prev) => *self.ends.get(prev)? as usize,
None => 0,
};
self.data.get(start..end)
}
pub(crate) fn iter(&self) -> impl Iterator<Item = &str> + '_ {
(0..self.len()).filter_map(|i| self.get(i))
}
}
#[derive(Clone, PartialEq, Eq, Debug, Default)]
pub struct Module {
pub(crate) strings: Strings,
pub(crate) types: Vec<TypeDef>,
pub(crate) consts: Vec<Const>,
pub(crate) imports: Vec<Import>,
pub(crate) globals: Vec<Global>,
pub(crate) functions: Vec<Function>,
pub(crate) exports: Vec<Export>,
pub(crate) hooks: Vec<HookBinding>,
pub(crate) name: Option<StrId>,
pub(crate) start: Option<FuncId>,
}
impl Module {
#[must_use]
pub fn string(&self, id: StrId) -> Option<&str> {
self.strings.get(id.index())
}
#[must_use]
pub fn string_count(&self) -> usize {
self.strings.len()
}
pub fn strings(&self) -> impl Iterator<Item = &str> + '_ {
self.strings.iter()
}
#[must_use]
pub fn types(&self) -> &[TypeDef] {
&self.types
}
#[must_use]
pub fn type_def(&self, id: TypeId) -> Option<&TypeDef> {
self.types.get(id.index())
}
#[must_use]
pub fn consts(&self) -> &[Const] {
&self.consts
}
#[must_use]
pub fn constant(&self, id: ConstId) -> Option<&Const> {
self.consts.get(id.index())
}
#[must_use]
pub fn imports(&self) -> &[Import] {
&self.imports
}
#[must_use]
pub fn import(&self, id: ImportId) -> Option<&Import> {
self.imports.get(id.index())
}
#[must_use]
pub fn globals(&self) -> &[Global] {
&self.globals
}
#[must_use]
pub fn global(&self, id: GlobalId) -> Option<&Global> {
self.globals.get(id.index())
}
#[must_use]
pub fn functions(&self) -> &[Function] {
&self.functions
}
#[must_use]
pub fn function(&self, id: FuncId) -> Option<&Function> {
self.functions.get(id.index())
}
#[must_use]
pub fn exports(&self) -> &[Export] {
&self.exports
}
#[must_use]
pub fn hooks(&self) -> &[HookBinding] {
&self.hooks
}
#[must_use]
pub fn hook(&self, hook: Hook) -> Option<Callee> {
self.hooks
.binary_search_by_key(&hook, |b| b.hook)
.ok()
.and_then(|i| self.hooks.get(i))
.map(|b| b.callee)
}
#[must_use]
pub fn name(&self) -> Option<StrId> {
self.name
}
#[must_use]
pub fn start(&self) -> Option<FuncId> {
self.start
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[non_exhaustive]
pub enum ErrorKind {
ArithOverflow,
DivByZero,
ShiftOutOfRange,
InvalidConversion,
InvalidChar,
TypeError,
NullReference,
IndexOutOfBounds,
KeyNotFound,
UndefinedProperty,
StackOverflow,
OutOfMemory,
OutOfFuel,
InvalidStrIndex,
Unreachable,
InvalidCoroState,
CannotSuspend,
NoScheduler,
CloseIgnored,
}
impl ErrorKind {
pub const ALL: &'static [ErrorKind] = &[
ErrorKind::ArithOverflow,
ErrorKind::DivByZero,
ErrorKind::ShiftOutOfRange,
ErrorKind::InvalidConversion,
ErrorKind::InvalidChar,
ErrorKind::TypeError,
ErrorKind::NullReference,
ErrorKind::IndexOutOfBounds,
ErrorKind::KeyNotFound,
ErrorKind::UndefinedProperty,
ErrorKind::StackOverflow,
ErrorKind::OutOfMemory,
ErrorKind::OutOfFuel,
ErrorKind::InvalidStrIndex,
ErrorKind::Unreachable,
ErrorKind::InvalidCoroState,
ErrorKind::CannotSuspend,
ErrorKind::NoScheduler,
ErrorKind::CloseIgnored,
];
#[must_use]
pub const fn code(self) -> u32 {
match self {
ErrorKind::ArithOverflow => 1,
ErrorKind::DivByZero => 2,
ErrorKind::ShiftOutOfRange => 3,
ErrorKind::InvalidConversion => 4,
ErrorKind::InvalidChar => 5,
ErrorKind::TypeError => 100,
ErrorKind::NullReference => 101,
ErrorKind::IndexOutOfBounds => 102,
ErrorKind::KeyNotFound => 103,
ErrorKind::UndefinedProperty => 104,
ErrorKind::StackOverflow => 105,
ErrorKind::OutOfMemory => 106,
ErrorKind::OutOfFuel => 107,
ErrorKind::InvalidStrIndex => 108,
ErrorKind::Unreachable => 109,
ErrorKind::InvalidCoroState => 110,
ErrorKind::CannotSuspend => 111,
ErrorKind::NoScheduler => 112,
ErrorKind::CloseIgnored => 113,
}
}
#[must_use]
pub fn from_code(code: u32) -> Option<Self> {
ErrorKind::ALL.iter().copied().find(|k| k.code() == code)
}
#[must_use]
pub const fn name(self) -> &'static str {
match self {
ErrorKind::ArithOverflow => "ArithOverflow",
ErrorKind::DivByZero => "DivByZero",
ErrorKind::ShiftOutOfRange => "ShiftOutOfRange",
ErrorKind::InvalidConversion => "InvalidConversion",
ErrorKind::InvalidChar => "InvalidChar",
ErrorKind::TypeError => "TypeError",
ErrorKind::NullReference => "NullReference",
ErrorKind::IndexOutOfBounds => "IndexOutOfBounds",
ErrorKind::KeyNotFound => "KeyNotFound",
ErrorKind::UndefinedProperty => "UndefinedProperty",
ErrorKind::StackOverflow => "StackOverflow",
ErrorKind::OutOfMemory => "OutOfMemory",
ErrorKind::OutOfFuel => "OutOfFuel",
ErrorKind::InvalidStrIndex => "InvalidStrIndex",
ErrorKind::Unreachable => "Unreachable",
ErrorKind::InvalidCoroState => "InvalidCoroState",
ErrorKind::CannotSuspend => "CannotSuspend",
ErrorKind::NoScheduler => "NoScheduler",
ErrorKind::CloseIgnored => "CloseIgnored",
}
}
#[must_use]
pub const fn is_catchable(self) -> bool {
!matches!(
self,
ErrorKind::OutOfMemory | ErrorKind::OutOfFuel | ErrorKind::Unreachable
)
}
}
impl fmt::Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "E{:04} {}", self.code(), self.name())
}
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use alloc::vec;
use super::*;
#[test]
fn strings_store_back_to_back() {
let mut s = Strings::default();
assert!(s.push("ab"));
assert!(s.push(""));
assert!(s.push("é"));
assert_eq!(s.get(0), Some("ab"));
assert_eq!(s.get(1), Some(""));
assert_eq!(s.get(2), Some("é"));
assert_eq!(s.get(3), None);
assert_eq!(s.iter().count(), 3);
}
#[test]
fn error_codes_are_unique_and_round_trip() {
for &kind in ErrorKind::ALL {
assert_eq!(ErrorKind::from_code(kind.code()), Some(kind));
}
assert_eq!(ErrorKind::ArithOverflow.to_string(), "E0001 ArithOverflow");
assert_eq!(ErrorKind::Unreachable.to_string(), "E0109 Unreachable");
}
#[test]
fn const_children_cover_arrays_and_maps() {
let map = Const::Map(vec![(ConstId(0), ConstId(1)), (ConstId(2), ConstId(3))]);
assert_eq!(
map.children().collect::<Vec<_>>(),
[ConstId(0), ConstId(1), ConstId(2), ConstId(3)]
);
assert_eq!(Const::Int(1).children().count(), 0);
}
#[test]
fn nan_constants_print_their_payload() {
assert_eq!(
Const::F64(0x7ff8_0000_0000_0001).to_string(),
"f64 nan:0x7ff8000000000001"
);
assert_eq!(Const::F32(0x7fc0_0000).to_string(), "f32 nan:0x7fc00000");
assert_eq!(
Const::Bytes(vec![b'a', 0xff]).to_string(),
"bytes b\"a\\xff\""
);
}
}