use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::cmp::Ordering;
use core::convert::TryFrom;
use core::fmt::{self, Display, Formatter};
use core::hash::{Hash as RustHash, Hasher};
use core::str::FromStr;
use amplify::{ByteArray, Bytes32};
use baid58::{Baid58ParseError, FromBaid58, ToBaid58};
use sha2::{Digest, Sha256};
use super::{Cursor, Read};
use crate::data::ByteStr;
use crate::isa::{BytecodeError, ExecStep, InstructionSet};
use crate::library::segs::IsaSeg;
use crate::library::{CodeEofError, LibSeg, LibSegOverflow, SegmentError};
use crate::reg::CoreRegs;
use crate::LIB_NAME_ALUVM;
pub const LIB_ID_TAG: [u8; 32] = *b"urn:ubideco:aluvm:lib:v01#230304";
#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Default, Debug, From)]
#[wrapper(Deref, BorrowSlice, Hex, Index, RangeOps)]
#[derive(StrictType, StrictDecode)]
#[cfg_attr(feature = "std", derive(StrictEncode))]
#[strict_type(lib = LIB_NAME_ALUVM)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", transparent)
)]
pub struct LibId(
#[from]
#[from([u8; 32])]
Bytes32,
);
impl ToBaid58<32> for LibId {
const HRI: &'static str = "alu";
fn to_baid58_payload(&self) -> [u8; 32] { self.to_byte_array() }
}
impl FromBaid58<32> for LibId {}
impl FromStr for LibId {
type Err = Baid58ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::from_baid58_str(s.trim_start_matches("urn:ubideco:"))
}
}
impl Display for LibId {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if f.sign_minus() {
write!(f, "urn:ubideco:{::<}", self.to_baid58())
} else {
write!(f, "urn:ubideco:{::<#}", self.to_baid58())
}
}
}
impl LibId {
pub fn with(
isae: impl AsRef<str>,
code: impl AsRef<[u8]>,
data: impl AsRef<[u8]>,
libs: &LibSeg,
) -> LibId {
let mut tagger = Sha256::default();
tagger.update(LIB_ID_TAG);
let tag = tagger.finalize();
let mut hasher = Sha256::default();
hasher.update(tag);
hasher.update(tag);
let isae = isae.as_ref();
let code = code.as_ref();
let data = data.as_ref();
hasher.update((isae.len() as u8).to_le_bytes());
hasher.update(isae.as_bytes());
hasher.update((code.len() as u16).to_le_bytes());
hasher.update(code);
hasher.update((data.len() as u16).to_le_bytes());
hasher.update(data);
hasher.update([libs.count()]);
for lib in libs {
hasher.update(lib.as_slice());
}
LibId::from_byte_array(hasher.finalize())
}
}
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))]
pub struct Lib {
pub isae: IsaSeg,
pub code: ByteStr,
pub data: ByteStr,
pub libs: LibSeg,
}
impl Display for Lib {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
writeln!(f, "ISAE: {}", &self.isae)?;
write!(f, "CODE:\n{:#10}", self.code)?;
write!(f, "DATA:\n{:#10}", self.data)?;
write!(f, "LIBS: {:8}", self.libs)
}
}
impl PartialEq for Lib {
#[inline]
fn eq(&self, other: &Self) -> bool { self.id().eq(&other.id()) }
}
impl Eq for Lib {}
impl PartialOrd for Lib {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
}
impl Ord for Lib {
#[inline]
fn cmp(&self, other: &Self) -> Ordering { self.id().cmp(&other.id()) }
}
impl RustHash for Lib {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) { state.write(&self.id()[..]) }
}
#[derive(Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, From)]
#[display(inner)]
pub enum AssemblerError {
#[from]
Bytecode(BytecodeError),
#[from]
LibSegOverflow(LibSegOverflow),
}
#[cfg(feature = "std")]
impl ::std::error::Error for AssemblerError {
fn source(&self) -> Option<&(dyn ::std::error::Error + 'static)> {
match self {
AssemblerError::Bytecode(err) => Some(err),
AssemblerError::LibSegOverflow(err) => Some(err),
}
}
}
impl Lib {
pub fn with(
isa: &str,
bytecode: Vec<u8>,
data: Vec<u8>,
libs: LibSeg,
) -> Result<Lib, SegmentError> {
let isae = IsaSeg::from_iter(isa.split(' '))?;
Ok(Self {
isae,
libs,
code: ByteStr::try_from(bytecode.as_slice())
.map_err(|_| SegmentError::CodeSegmentTooLarge(bytecode.len()))?,
data: ByteStr::try_from(data.as_slice())
.map_err(|_| SegmentError::DataSegmentTooLarge(bytecode.len()))?,
})
}
pub fn assemble<Isa>(code: &[Isa]) -> Result<Lib, AssemblerError>
where
Isa: InstructionSet,
{
let call_sites = code.iter().filter_map(|instr| instr.call_site());
let libs_segment = LibSeg::with(call_sites)?;
let mut code_segment = ByteStr::default();
let mut writer = Cursor::<_, ByteStr>::new(&mut code_segment.bytes[..], &libs_segment);
for instr in code.iter() {
instr.encode(&mut writer)?;
}
let pos = writer.pos();
let data_segment = writer.into_data_segment();
code_segment.adjust_len(pos);
Ok(Lib {
isae: IsaSeg::from_iter(Isa::isa_ids())
.expect("ISA instruction set contains incorrect ISAE ids"),
libs: libs_segment,
code: code_segment,
data: data_segment,
})
}
pub fn disassemble<Isa>(&self) -> Result<Vec<Isa>, CodeEofError>
where
Isa: InstructionSet,
{
let mut code = Vec::new();
let mut reader = Cursor::with(&self.code, &self.data, &self.libs);
while !reader.is_eof() {
code.push(Isa::decode(&mut reader)?);
}
Ok(code)
}
#[inline]
pub fn id(&self) -> LibId {
LibId::with(self.isae_segment(), &self.code, &self.data, &self.libs)
}
#[inline]
pub fn isae_segment(&self) -> String { self.isae.to_string() }
#[inline]
pub fn code_segment(&self) -> &[u8] { self.code.as_ref() }
#[inline]
pub fn data_segment(&self) -> &[u8] { self.data.as_ref() }
#[inline]
pub fn libs_segment(&self) -> &LibSeg { &self.libs }
pub fn exec<Isa>(
&self,
entrypoint: u16,
registers: &mut CoreRegs,
context: &Isa::Context<'_>,
) -> Option<LibSite>
where
Isa: InstructionSet,
{
let mut cursor = Cursor::with(&self.code.bytes[..], &self.data, &self.libs);
let lib_hash = self.id();
cursor.seek(entrypoint).ok()?;
while !cursor.is_eof() {
let pos = cursor.pos();
let instr = Isa::decode(&mut cursor).ok()?;
let next = instr.exec(registers, LibSite::with(pos, lib_hash), context);
#[cfg(all(debug_assertions, feature = "std"))]
eprint!("\n@{:06}> {:48}; st0={}", pos, instr, registers.st0);
if !registers.acc_complexity(instr) {
#[cfg(all(debug_assertions, feature = "std"))]
eprintln!();
return None;
}
match next {
ExecStep::Stop => {
#[cfg(all(debug_assertions, feature = "std"))]
eprintln!();
return None;
}
ExecStep::Next => continue,
ExecStep::Jump(pos) => {
#[cfg(all(debug_assertions, feature = "std"))]
eprint!(" -> {}", pos);
cursor.seek(pos).ok()?;
}
ExecStep::Call(site) => {
#[cfg(all(debug_assertions, feature = "std"))]
eprint!(" -> {}", site);
return Some(site);
}
}
}
None
}
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default, Display)]
#[derive(StrictType, StrictDecode)]
#[strict_type(lib = LIB_NAME_ALUVM)]
#[cfg_attr(feature = "std", derive(StrictEncode))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))]
#[display("{pos} @ {lib}")]
pub struct LibSite {
pub lib: LibId,
pub pos: u16,
}
impl LibSite {
pub fn with(pos: u16, lib: LibId) -> LibSite { LibSite { lib, pos } }
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn lib_id_display() {
let id = LibId::with("FLOAT", &b"", &b"", &none!());
assert_eq!(
format!("{id}"),
"urn:ubideco:alu:GrjjwmeTsibiEeYYtjokmc8j4Jn1KWL2SX8NugG6T5kZ#pinball-eternal-colombo"
);
assert_eq!(
format!("{id:-}"),
"urn:ubideco:alu:GrjjwmeTsibiEeYYtjokmc8j4Jn1KWL2SX8NugG6T5kZ"
);
}
#[test]
fn lib_id_from_str() {
let id = LibId::with("FLOAT", &b"", &b"", &none!());
assert_eq!(
Ok(id),
LibId::from_str(
"urn:ubideco:alu:GrjjwmeTsibiEeYYtjokmc8j4Jn1KWL2SX8NugG6T5kZ#\
pinball-eternal-colombo"
)
);
assert_eq!(
Ok(id),
LibId::from_str("urn:ubideco:alu:GrjjwmeTsibiEeYYtjokmc8j4Jn1KWL2SX8NugG6T5kZ")
);
assert_eq!(
Ok(id),
LibId::from_str(
"alu:GrjjwmeTsibiEeYYtjokmc8j4Jn1KWL2SX8NugG6T5kZ#pinball-eternal-colombo"
)
);
assert_eq!(Ok(id), LibId::from_str("alu:GrjjwmeTsibiEeYYtjokmc8j4Jn1KWL2SX8NugG6T5kZ"));
assert_eq!(Ok(id), LibId::from_str("GrjjwmeTsibiEeYYtjokmc8j4Jn1KWL2SX8NugG6T5kZ"));
}
}