use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use core::fmt::{self, Display, Formatter};
use core::marker::PhantomData;
use bitcoin_hashes::Hash;
use crate::instr::serialize::{compile, Bytecode, EncodeError};
use crate::instr::{ExecStep, NOp};
use crate::{Cursor, Instr, InstructionSet, Registers};
const LIB_HASH_MIDSTATE: [u8; 32] = [
156, 224, 228, 230, 124, 17, 108, 57, 56, 179, 202, 242, 195, 15, 80, 137, 211, 243, 147, 108,
71, 99, 110, 96, 125, 179, 62, 234, 221, 198, 240, 201,
];
sha256t_hash_newtype!(
LibHash,
LibHashTag,
LIB_HASH_MIDSTATE,
64,
doc = "Library reference: a hash of the library code",
false
);
#[derive(Debug, Display)]
#[display("{bytecode}", alt = "{bytecode:#}")]
pub struct Lib<E = NOp>
where
E: InstructionSet,
{
bytecode: ByteStr,
instruction_set: PhantomData<E>,
}
impl<E> Lib<E>
where
E: InstructionSet,
{
pub fn with<I>(code: I) -> Result<Lib<E>, EncodeError>
where
I: IntoIterator,
<I as IntoIterator>::Item: InstructionSet,
{
let bytecode = compile::<E, _>(code)?;
Ok(Lib { bytecode, instruction_set: PhantomData::<E>::default() })
}
pub fn lib_hash(&self) -> LibHash { LibHash::hash(&*self.bytecode.bytes) }
pub fn byte_count(&self) -> u16 { self.bytecode.len }
pub fn bytecode(&self) -> &[u8] { &self.bytecode.as_ref() }
pub fn run(&self, entrypoint: u16, registers: &mut Registers) -> Option<LibSite> {
let mut cursor = Cursor::with(&self.bytecode.bytes[..]);
let lib_hash = self.lib_hash();
cursor.seek(entrypoint);
while !cursor.is_eof() {
let instr = Instr::<E>::read(&mut cursor).ok()?;
match instr.exec(registers, LibSite::with(cursor.pos(), lib_hash)) {
ExecStep::Stop => return None,
ExecStep::Next => continue,
ExecStep::Jump(pos) => cursor.seek(pos),
ExecStep::Call(site) => return Some(site),
}
}
None
}
}
impl<E> AsRef<[u8]> for Lib<E>
where
E: InstructionSet,
{
fn as_ref(&self) -> &[u8] { self.bytecode.as_ref() }
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default, Display)]
#[display("{pos:#06X}@{lib}")]
pub struct LibSite {
pub lib: LibHash,
pub pos: u16,
}
impl LibSite {
pub fn with(pos: u16, lib: LibHash) -> LibSite { LibSite { lib, pos } }
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct ByteStr {
pub len: u16,
pub bytes: Box<[u8; u16::MAX as usize]>,
}
impl Default for ByteStr {
fn default() -> ByteStr { ByteStr { len: 0, bytes: Box::new([0u8; u16::MAX as usize]) } }
}
impl AsRef<[u8]> for ByteStr {
fn as_ref(&self) -> &[u8] { &self.bytes[..self.len as usize] }
}
impl ByteStr {
pub fn with(slice: impl AsRef<[u8]>) -> ByteStr {
let len = slice.as_ref().len();
let mut bytes = [0u8; u16::MAX as usize];
bytes[0..len].copy_from_slice(slice.as_ref());
ByteStr { len: len as u16, bytes: Box::new(bytes) }
}
}
#[cfg(feature = "std")]
impl Display for ByteStr {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
use amplify_num::hex::ToHex;
let vec = Vec::from(&self.bytes[..self.len as usize]);
if let Ok(s) = String::from_utf8(vec) {
f.write_str("\"")?;
f.write_str(&s)?;
f.write_str("\"")
} else if f.alternate() && self.len > 4 {
write!(
f,
"{}..{}",
self.bytes[..4].to_hex(),
self.bytes[(self.len as usize - 4)..].to_hex()
)
} else {
f.write_str(&self.bytes[0usize..(self.len as usize)].to_hex())
}
}
}
#[cfg(not(feature = "std"))]
impl Display for ByteStr {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let vec = Vec::from(&self.bytes[..self.len as usize]);
write!(f, "{:#04X?}", &self.bytes[0usize..(self.len as usize)])
}
}
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)]
#[display("call to unknown library {0:#}")]
#[cfg_attr(feature = "std", derive(Error))]
pub struct NoLibraryError(LibHash);
#[derive(Getters, Debug, Default)]
pub struct Runtime<E = NOp>
where
E: InstructionSet,
{
libs: BTreeMap<LibHash, Lib<E>>,
entrypoint: LibSite,
registers: Registers,
}
impl<E> Runtime<E>
where
E: InstructionSet,
{
pub fn new() -> Runtime<E> {
Runtime {
libs: Default::default(),
entrypoint: Default::default(),
registers: Default::default(),
}
}
pub fn with(lib: Lib<E>) -> Runtime<E> {
let mut runtime = Runtime::new();
runtime.entrypoint = LibSite::with(0, lib.lib_hash());
runtime.add_lib(lib);
runtime
}
pub fn add_lib(&mut self, lib: Lib<E>) -> bool {
self.libs.insert(lib.lib_hash(), lib).is_none()
}
pub fn set_entrypoint(&mut self, entrypoint: LibSite) { self.entrypoint = entrypoint; }
pub fn main(&mut self) -> Result<bool, NoLibraryError> { self.call(self.entrypoint) }
pub fn call(&mut self, mut method: LibSite) -> Result<bool, NoLibraryError> {
while let Some(m) = self
.libs
.get(&method.lib)
.ok_or(NoLibraryError(method.lib))?
.run(method.pos, &mut self.registers)
{
method = m
}
Ok(self.registers.st0)
}
}