#[cfg(feature="compiler")]
pub mod writer;
#[macro_use]
pub mod impl_opcodes;
#[macro_use]
pub mod impl_builtins;
pub mod opcodes;
pub mod builtins;
#[path="compiler/compiler.rs"]
#[cfg(feature="compiler")]
pub mod compiler;
#[path="runtime/runtime.rs"]
pub mod runtime;
use crate::prelude::*;
#[cfg(feature="compiler")]
use writer::{Writer, StoreConst};
use crate::{StackAddress, StackOffset, HeapAddress, HEAP_OFFSET_BITS, ItemIndex, RustFnIndex};
use crate::bytecode::runtime::{vm::VM, stack::{Stack, StackOp}};
const ARG1: StackOffset = 0;
const ARG2: StackOffset = 4;
const ARG3: StackOffset = 8;
pub trait VMFunc<T>: Debug {
#[doc(hidden)]
fn from_index(index: RustFnIndex) -> Self;
#[doc(hidden)]
fn into_index(self: Self) -> RustFnIndex;
#[doc(hidden)]
fn resolve_info() -> UnorderedMap<&'static str, (RustFnIndex, &'static str, Vec<&'static str>)>;
}
pub trait VMData<T: VMFunc<T>, U> {
#[doc(hidden)]
fn exec(self: Self, vm: &mut VM<T, U>, context: &mut U);
}
#[derive(Clone, Debug)]
pub struct Program<T> {
rust_fn: PhantomData<T>,
pub(crate) instructions : Vec<u8>,
pub(crate) consts : Vec<u8>,
pub(crate) const_descriptors: Vec<ConstDescriptor>,
}
fn read<'a>(slice: &mut &'a[ u8 ], num_bytes: usize) -> Option<&'a[ u8 ]> {
if slice.len() < num_bytes {
None
} else {
let result = &slice[0..num_bytes];
*slice = &slice[num_bytes..];
Some(result)
}
}
impl<T> Program<T> where T: VMFunc<T> {
#[cfg(feature="compiler")]
pub(crate) fn new() -> Self {
Program {
rust_fn : PhantomData,
instructions : Vec::new(),
consts : Vec::new(),
const_descriptors : Vec::new(),
}
}
pub fn to_bytes(self: &Self) -> Vec<u8> {
let mut result = Vec::new();
result.extend_from_slice("itsy".as_bytes());
result.extend_from_slice(&self.instructions.len().to_le_bytes()[..]);
result.extend_from_slice(&self.instructions[..]);
result.extend_from_slice(&self.consts.len().to_le_bytes()[..]);
result.extend_from_slice(&self.consts[..]);
result.extend_from_slice(&self.const_descriptors.len().to_le_bytes()[..]);
for descriptor in &self.const_descriptors {
result.extend_from_slice(&descriptor.to_bytes()[..]);
}
result
}
pub fn from_bytes(mut program: &[ u8 ]) -> Option<Program<T>> {
const USIZE: usize = size_of::<usize>();
if read(&mut program, 4)? != "itsy".as_bytes() {
return None;
}
let instructions_size: usize = usize::from_le_bytes(read(&mut program, USIZE)?.try_into().ok()?);
let instructions: Vec<u8> = read(&mut program, instructions_size)?.into();
let consts_size: usize = usize::from_le_bytes(read(&mut program, USIZE)?.try_into().ok()?);
let consts: Vec<u8> = read(&mut program, consts_size)?.into();
let const_descriptors_size: usize = usize::from_le_bytes(read(&mut program, USIZE)?.try_into().ok()?);
let mut const_descriptors: Vec<ConstDescriptor> = Vec::new();
for _ in 0..const_descriptors_size {
const_descriptors.push(ConstDescriptor::from_bytes(read(&mut program, ConstDescriptor::SERIALIZED_SIZE)?)?);
}
Some(Self {
rust_fn: PhantomData,
instructions,
consts,
const_descriptors,
})
}
}
#[derive(Clone, Copy, Debug)]
#[repr(u8)]
#[allow(dead_code)]
pub(crate) enum ConstEndianness {
None = 0,
Integer = 1,
Float = 2,
}
impl ConstEndianness {
pub(crate) fn from_u8(index: u8) -> Option<Self> {
match index {
x if x == Self::None as u8 => Some(Self::None),
x if x == Self::Integer as u8 => Some(Self::Integer),
x if x == Self::Float as u8 => Some(Self::Float),
_ => None
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct ConstDescriptor { pub(crate) position : StackAddress,
pub(crate) size : StackAddress,
pub(crate) endianness : ConstEndianness,
}
impl ConstDescriptor {
const SERIALIZED_SIZE: usize = 9;
fn to_bytes(self: &Self) -> [ u8; Self::SERIALIZED_SIZE ] {
let mut result = [ 0u8; Self::SERIALIZED_SIZE ];
let position = (self.position as u32).to_le_bytes();
let size = (self.size as u32).to_le_bytes();
let position_slice = &mut result[0..4];
position_slice.copy_from_slice(&position);
let size_slice = &mut result[4..8];
size_slice.copy_from_slice(&size);
result[8] = self.endianness as u8;
result
}
fn from_bytes(mut descriptor: &[ u8 ]) -> Option<Self> {
const U32: usize = size_of::<u32>();
let position = u32::from_le_bytes(read(&mut descriptor, U32)?.try_into().ok()?) as usize;
let size = u32::from_le_bytes(read(&mut descriptor, U32)?.try_into().ok()?) as usize;
let endianness = ConstEndianness::from_u8(read(&mut descriptor, 1)?[0])?;
Some(Self {
position,
size,
endianness,
})
}
}
#[repr(u8)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Constructor {
Primitive = 174, Array = 176, Struct = 177, String = 178, Enum = 179, }
pub struct ConstructorData {
pub op: Constructor,
pub offset: StackAddress,
pub next: StackAddress,
}
impl Constructor {
pub fn from_u8(raw: u8) -> Constructor {
match raw {
x if x == Self::Primitive as u8 => Self::Primitive,
x if x == Self::Array as u8 => Self::Array,
x if x == Self::Struct as u8 => Self::Struct,
x if x == Self::String as u8 => Self::String,
x if x == Self::Enum as u8 => Self::Enum,
index @ _ => unreachable!("Invalid constructor type {}", index),
}
}
pub fn to_u8(self: Self) -> u8 {
self as u8
}
pub fn parse(stack: &Stack, mut offset: StackAddress) -> ConstructorData {
let constructor = Constructor::from_u8(stack.load(offset));
offset += size_of_val(&constructor) as StackAddress;
Self::parse_with(stack, offset, constructor)
}
pub fn parse_with(stack: &Stack, mut offset: StackAddress, constructor: Constructor) -> ConstructorData {
match constructor {
Constructor::Array | Constructor::Struct | Constructor::Enum => {
let constructor_size: ItemIndex = stack.load(offset);
offset += size_of_val(&constructor_size) as StackAddress;
ConstructorData {
op: constructor,
offset,
next: offset + constructor_size as StackAddress,
}
},
Constructor::Primitive => {
const SIZE: StackAddress = size_of::<ItemIndex>() as StackAddress;
ConstructorData {
op: constructor,
offset,
next: offset + SIZE,
}
},
Constructor::String => {
ConstructorData {
op: constructor,
offset,
next: offset,
}
},
}
}
pub fn parse_primitive(stack: &Stack, offset: StackAddress) -> ItemIndex {
stack.load(offset)
}
pub fn parse_struct(stack: &Stack, mut offset: StackAddress) -> (ItemIndex, ItemIndex, StackAddress) {
let implementor_index: ItemIndex = stack.load(offset);
offset += size_of_val(&implementor_index) as StackAddress;
let num_fields: ItemIndex = stack.load(offset);
(implementor_index, num_fields, offset + size_of_val(&implementor_index) as StackAddress)
}
pub fn parse_variant_table(stack: &Stack, mut offset: StackAddress, variant_index: ItemIndex) -> (ItemIndex, StackAddress) {
offset += (size_of::<StackAddress>() * variant_index as usize) as StackAddress;
offset = stack.load(offset);
let num_fields = stack.load(offset);
(num_fields, offset + size_of_val(&num_fields) as StackAddress)
}
}
#[derive(Copy, Clone, PartialOrd)]
pub struct HeapRef {
address: HeapAddress,
}
impl HeapRef {
const INDEX_MASK: HeapAddress = !((1 << HEAP_OFFSET_BITS) - 1);
const OFFSET_MASK: HeapAddress = (1 << HEAP_OFFSET_BITS) - 1;
pub fn new(index: StackAddress, offset: StackAddress) -> HeapRef {
Self { address: ((index as HeapAddress) << HEAP_OFFSET_BITS) | (offset as HeapAddress) }
}
#[cfg_attr(not(debug_assertions), inline(always))]
pub fn index(self: &Self) -> StackAddress {
((self.address & HeapRef::INDEX_MASK) >> HEAP_OFFSET_BITS) as StackAddress
}
#[cfg_attr(not(debug_assertions), inline(always))]
pub fn offset(self: &Self) -> StackAddress {
(self.address & HeapRef::OFFSET_MASK) as StackAddress
}
#[cfg_attr(not(debug_assertions), inline(always))]
pub fn add_offset(self: &mut Self, offset: StackOffset) {
debug_assert!((self.offset() as StackOffset + offset) as HeapAddress <= (1 << HEAP_OFFSET_BITS) - 1);
let new_offset = self.offset() as StackOffset + offset;
let index = self.index();
self.address = ((index as HeapAddress) << HEAP_OFFSET_BITS) | (new_offset as HeapAddress)
}
#[cfg_attr(not(debug_assertions), inline(always))]
pub fn with_offset(self: Self, offset: StackOffset) -> Self {
debug_assert!((self.offset() as StackOffset + offset) as HeapAddress <= (1 << HEAP_OFFSET_BITS) - 1);
HeapRef::new(self.index(), (self.offset() as StackOffset + offset) as StackAddress)
}
pub fn to_ne_bytes(self: &Self) -> [ u8; size_of::<HeapAddress>() ] {
self.address.to_ne_bytes()
}
pub fn from_ne_bytes(bytes: [ u8; size_of::<HeapAddress>() ]) -> Self {
HeapRef { address: HeapAddress::from_ne_bytes(bytes) }
}
pub const fn primitive_size() -> u8 {
size_of::<Self>() as u8
}
}
impl From<HeapRef> for (StackAddress, StackAddress) {
fn from(heap_ref: HeapRef) -> (StackAddress, StackAddress) {
(
((heap_ref.address & HeapRef::INDEX_MASK) >> HEAP_OFFSET_BITS) as StackAddress,
(heap_ref.address & HeapRef::OFFSET_MASK) as StackAddress
)
}
}
impl PartialEq for HeapRef {
fn eq(&self, other: &Self) -> bool {
self.address == other.address
}
}
impl Debug for HeapRef {
fn fmt(self: &Self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "HeapRef(index:{}, offset:{})", self.index(), self.offset())
}
}