diff --git a/Cargo.lock b/Cargo.lock index 0ecc546d0..64583c237 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -172,6 +172,16 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +[[package]] +name = "bitcode" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee1bce7608560cd4bf0296a4262d0dbf13e6bcec5ff2105724c8ab88cc7fc784" +dependencies = [ + "bytemuck", + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -229,6 +239,12 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "bytemuck" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" + [[package]] name = "byteorder" version = "1.5.0" @@ -1025,6 +1041,7 @@ dependencies = [ "pretty_assertions", "regex", "rustc_version 0.4.1", + "serde", "smallvec", "tracing", "unicode-width 0.2.0", @@ -1131,6 +1148,18 @@ dependencies = [ "syn", ] +[[package]] +name = "miden-package" +version = "0.11.0" +dependencies = [ + "bitcode", + "miden-assembly", + "miden-core", + "serde", + "serde_bytes", + "serde_repr", +] + [[package]] name = "miden-processor" version = "0.11.0" @@ -1139,6 +1168,7 @@ dependencies = [ "miden-air", "miden-assembly", "miden-core", + "miden-package", "miden-test-utils", "tracing", "winter-fri", @@ -1874,6 +1904,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.214" @@ -1897,6 +1936,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_spanned" version = "0.6.8" @@ -1953,6 +2003,9 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] [[package]] name = "smawk" diff --git a/Cargo.toml b/Cargo.toml index 30d5cdbdf..ac1587911 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "assembly", "core", "miden", + "package", "processor", "prover", "stdlib", diff --git a/assembly/Cargo.toml b/assembly/Cargo.toml index e72f780fb..712223bed 100644 --- a/assembly/Cargo.toml +++ b/assembly/Cargo.toml @@ -21,6 +21,10 @@ doctest = false default = ["std"] std = ["aho-corasick/std", "miette/fancy", "miette/std", "thiserror/std", "vm-core/std"] testing = ["dep:regex"] +serde = [ + "dep:serde", + "smallvec/serde", +] [dependencies] aho-corasick = { version = "1.1", default-features = false } @@ -37,6 +41,7 @@ unicode-width = { version = "0.2", features = ["no_std"] } vm-core = { package = "miden-core", path = "../core", version = "0.11", default-features = false, features = [ "diagnostics", ] } +serde = { version = "1.0.208", features = ["alloc", "rc", "serde_derive"], optional = true } [dev-dependencies] pretty_assertions = "1.4" diff --git a/assembly/src/ast/ident.rs b/assembly/src/ast/ident.rs index ea2d13a23..10e999ef4 100644 --- a/assembly/src/ast/ident.rs +++ b/assembly/src/ast/ident.rs @@ -183,3 +183,38 @@ impl FromStr for Ident { Ok(Self { span: SourceSpan::default(), name }) } } + +#[cfg(feature = "serde")] +impl serde::Serialize for Ident { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.as_str().serialize(serializer) + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for Ident { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct IdentVisitor; + impl serde::de::Visitor<'_> for IdentVisitor { + type Value = Ident; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("ident") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + Ok(Ident::new_unchecked(Span::unknown(Arc::from(v)))) + } + } + deserializer.deserialize_str(IdentVisitor) + } +} diff --git a/core/src/utils/mod.rs b/core/src/utils/mod.rs index f610ed7f8..3cbf5fb70 100644 --- a/core/src/utils/mod.rs +++ b/core/src/utils/mod.rs @@ -41,6 +41,23 @@ impl ToElements for Vec { } } +// TODO(denysz): add roundtrip proptest +impl ToElements for &[u8] { + fn to_elements(&self) -> Vec { + self.chunks(4) + .map(|chunk| { + if chunk.len() < 4 { + let mut bytes = [0; 4]; + bytes[..chunk.len()].copy_from_slice(chunk); + Felt::new(u32::from_le_bytes(bytes) as u64) + } else { + Felt::new(u32::from_le_bytes(chunk.try_into().unwrap()) as u64) + } + }) + .collect() + } +} + // INTO BYTES // ================================================================================================ diff --git a/package/Cargo.toml b/package/Cargo.toml new file mode 100644 index 000000000..a9f524b1d --- /dev/null +++ b/package/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "miden-package" +version = "0.11.0" +description = "Miden VM package" +documentation = "https://docs.rs/miden-package/0.10.5" +readme = "README.md" +categories = ["compilers", "no-std"] +keywords = ["package", "language", "miden"] +license.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true +rust-version.workspace = true +edition.workspace = true + +[lib] +bench = false +doctest = false + +[dependencies] +vm-core = { package = "miden-core", path = "../core", version = "0.11", default-features = false } +# processor = { package = "miden-processor", path = "../processor", version = "0.11", default-features = false } +assembly = { package = "miden-assembly", path = "../assembly", version = "0.11", default-features = false, features = [ + "serde", +] } +serde = { version = "1.0.208", features = ["alloc", "rc", "serde_derive"] } +serde_bytes = "0.11.15" +serde_repr = "0.1.19" +bitcode = { version = "0.6.3", default-features = false, features = ["serde"] } + +[dev-dependencies] diff --git a/package/README.md b/package/README.md new file mode 100644 index 000000000..747fc473c --- /dev/null +++ b/package/README.md @@ -0,0 +1,7 @@ +## Overview + +TBD + +## Binary Format + +TBD \ No newline at end of file diff --git a/package/src/de.rs b/package/src/de.rs new file mode 100644 index 000000000..faa7e913c --- /dev/null +++ b/package/src/de.rs @@ -0,0 +1,57 @@ +use alloc::{fmt, sync::Arc}; + +use assembly::Library; +use vm_core::{utils::Deserializable, Program}; + +use crate::{package::MastArtifact, Digest}; + +/// Deserialize a [Digest] from a byte array +pub fn deserialize_digest<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + const DIGEST_BYTES: usize = 32; + + let bytes: [u8; DIGEST_BYTES] = serde_bytes::deserialize(deserializer)?; + + Digest::try_from(bytes).map_err(serde::de::Error::custom) +} + +/// Deserialize a [MastArtifact] from a byte array +pub fn deserialize_mast<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + struct MastArtifactVisitor; + + impl serde::de::Visitor<'_> for MastArtifactVisitor { + type Value = MastArtifact; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("mast artifact") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: serde::de::Error, + { + if let Some(bytes) = v.strip_prefix(b"PRG\0") { + Program::read_from_bytes(bytes) + .map(Arc::new) + .map(MastArtifact::Executable) + .map_err(serde::de::Error::custom) + } else if let Some(bytes) = v.strip_prefix(b"LIB\0") { + Library::read_from_bytes(bytes) + .map(Arc::new) + .map(MastArtifact::Library) + .map_err(serde::de::Error::custom) + } else { + Err(serde::de::Error::invalid_value( + serde::de::Unexpected::Bytes(v.get(0..4).unwrap_or(v)), + &"expected valid mast artifact type tag", + )) + } + } + } + deserializer.deserialize_bytes(MastArtifactVisitor) +} diff --git a/package/src/dep/mod.rs b/package/src/dep/mod.rs new file mode 100644 index 000000000..6daa61358 --- /dev/null +++ b/package/src/dep/mod.rs @@ -0,0 +1,67 @@ +use alloc::string::String; + +use serde::{Deserialize, Serialize}; + +use super::{de, se}; +use crate::Digest; + +pub(crate) mod resolver; + +/// A system library identifier +#[derive( + Debug, Copy, Clone, PartialEq, Eq, serde_repr::Serialize_repr, serde_repr::Deserialize_repr, +)] +#[repr(u8)] +pub enum SystemLibraryId { + /// The standard library + Stdlib, + /// The base library + Miden, +} + +impl core::str::FromStr for SystemLibraryId { + type Err = (); + fn from_str(s: &str) -> Result { + match s { + // Compiler uses "std" and "base" to identify the standard and base libraries + // respectively. We also accept "stdlib" and "miden" as aliases for these libraries. + "std" | "stdlib" => Ok(Self::Stdlib), + "base" | "miden" => Ok(Self::Miden), + _ => Err(()), + } + } +} + +/// The name of a dependency +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum DependencyName { + /// The dependency is a system library + System(SystemLibraryId), + /// The dependency is a user library with the given name + User(String), +} + +impl From for DependencyName { + fn from(s: String) -> Self { + if let Ok(id) = s.parse() { + DependencyName::System(id) + } else { + DependencyName::User(s) + } + } +} + +/// A package dependency +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Dependency { + /// The name of the dependency. + /// Serves as a human-readable identifier for the dependency and a search hint for the resolver + pub name: DependencyName, + /// The digest of the dependency. + /// Serves as an ultimate source of truth for identifying the dependency. + #[serde( + serialize_with = "se::serialize_digest", + deserialize_with = "de::deserialize_digest" + )] + pub digest: Digest, +} diff --git a/package/src/dep/resolver.rs b/package/src/dep/resolver.rs new file mode 100644 index 000000000..9f92b7306 --- /dev/null +++ b/package/src/dep/resolver.rs @@ -0,0 +1,69 @@ +use alloc::{collections::BTreeMap, sync::Arc}; + +use assembly::Library; + +use super::Dependency; +use crate::{Digest, Package}; + +// DEPENDENCY RESOLUTION +// ================================================================================================ + +#[derive(Debug, Clone)] +pub enum DependencyResolution { + Local(LocalResolution), + // Registry(...), +} + +impl From> for DependencyResolution { + fn from(library: Arc) -> Self { + Self::Local(LocalResolution::Library(library)) + } +} + +impl From> for DependencyResolution { + fn from(package: Arc) -> Self { + Self::Local(LocalResolution::Package(package)) + } +} + +#[derive(Debug, Clone)] +pub enum LocalResolution { + Library(Arc), + Package(Arc), +} + +impl From> for LocalResolution { + fn from(library: Arc) -> Self { + Self::Library(library) + } +} + +impl From> for LocalResolution { + fn from(package: Arc) -> Self { + Self::Package(package) + } +} + +// RESOLVER +// ================================================================================================ + +pub trait DependencyResolver { + fn resolve(&self, dependency: &Dependency) -> Option; +} + +#[derive(Debug, Default)] +pub struct MemDependencyResolverByDigest { + resolved: BTreeMap, +} + +impl MemDependencyResolverByDigest { + pub fn add(&mut self, digest: Digest, resolution: DependencyResolution) { + self.resolved.insert(digest, resolution); + } +} + +impl DependencyResolver for MemDependencyResolverByDigest { + fn resolve(&self, dependency: &Dependency) -> Option { + self.resolved.get(&dependency.digest).cloned() + } +} diff --git a/package/src/lib.rs b/package/src/lib.rs new file mode 100644 index 000000000..376d6fcb1 --- /dev/null +++ b/package/src/lib.rs @@ -0,0 +1,34 @@ +//! The [Package] containing a [vm_core::Program] or [assembly::Library], rodata segments, +//! and a manifest(exports and dependencies). +//! +//! Serves as the unit of deployment and distribution in the Miden ecosystem. +//! Contains everything needed to execute a program or link a library. + +#![no_std] + +extern crate alloc; + +mod de; +mod dep; +mod package; +mod rodata; +mod se; + +#[cfg(test)] +extern crate std; +#[cfg(test)] +mod tests; + +pub use self::{ + dep::{ + resolver::{ + DependencyResolution, DependencyResolver, LocalResolution, + MemDependencyResolverByDigest, + }, + Dependency, DependencyName, SystemLibraryId, + }, + package::{MastArtifact, Package, PackageExport, PackageManifest}, + rodata::{PtrDesc, Rodata}, +}; + +type Digest = vm_core::chiplets::hasher::Digest; diff --git a/package/src/package.rs b/package/src/package.rs new file mode 100644 index 000000000..efc94a7f2 --- /dev/null +++ b/package/src/package.rs @@ -0,0 +1,258 @@ +use alloc::{ + collections::{BTreeMap, BTreeSet}, + format, + string::String, + sync::Arc, + vec::Vec, +}; +use core::fmt; + +use assembly::{ast::QualifiedProcedureName, Library, Report}; +use serde::{Deserialize, Serialize}; +use vm_core::{mast::MastForest, utils::DisplayHex, Felt, Program}; + +use super::{de, se}; +use crate::{Dependency, Digest, Rodata}; + +// MAST ARTIFACT +// ================================================================================================ + +/// The artifact produced by lowering a [Program] to a Merkelized Abstract Syntax Tree +/// +/// This type is used in compilation pipelines to abstract over the type of output requested. +#[derive(Debug, Clone)] +pub enum MastArtifact { + /// A MAST artifact which can be executed by the VM directly + Executable(Arc), + /// A MAST artifact which can be used as a dependency by a [Program] + Library(Arc), +} + +impl MastArtifact { + /// Get the underlying [Program] for this artifact, or panic if this is a [Library] + pub fn unwrap_program(self) -> Arc { + match self { + Self::Executable(prog) => prog, + Self::Library(_) => panic!("attempted to unwrap 'mast' library as program"), + } + } + + /// Get the underlying [Library] for this artifact, or panic if this is a [Program] + pub fn unwrap_library(self) -> Arc { + match self { + Self::Executable(_) => panic!("attempted to unwrap 'mast' program as library"), + Self::Library(lib) => lib, + } + } + + /// Get the content digest associated with this artifact + pub fn digest(&self) -> Digest { + match self { + Self::Executable(ref prog) => prog.hash(), + Self::Library(ref lib) => *lib.digest(), + } + } + + /// Get the underlying [MastForest] for this artifact + pub fn mast_forest(&self) -> &MastForest { + match self { + Self::Executable(ref prog) => prog.mast_forest(), + Self::Library(ref lib) => lib.mast_forest(), + } + } +} + +// PACKAGE MANIFEST +// ================================================================================================ + +/// The manifest of a package, containing the set of package dependencies(libraries or packages) and +/// exported procedures and their signatures, if known. +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(default)] +pub struct PackageManifest { + /// The set of exports in this package. + pub exports: BTreeSet, + /// The libraries(packages) linked against by this package, which must be provided when + /// executing the program. + pub dependencies: Vec, +} + +/// A procedure exported by a package, along with its digest and +/// signature(will be added after MASM type attributes are implemented). +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Ord, PartialOrd)] +pub struct PackageExport { + /// The fully-qualified name of the procedure exported by this package + pub name: String, + /// The digest of the procedure exported by this package + #[serde( + serialize_with = "se::serialize_digest", + deserialize_with = "de::deserialize_digest" + )] + pub digest: Digest, + // Signature will be added in the future when the type signatures are available in the Assembly + // #[serde(default)] + // pub signature: Option, +} + +impl fmt::Debug for PackageExport { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("PackageExport") + .field("name", &format_args!("{}", self.name)) + .field("digest", &format_args!("{}", DisplayHex::new(&self.digest.as_bytes()))) + // .field("signature", &self.signature) + .finish() + } +} + +// PACKAGE +// ================================================================================================ + +/// A package containing a [Program]/[Library], rodata segments, +/// and a manifest(exports and dependencies). +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Package { + /// Name of the package + pub name: String, + /// Content digest of the package + #[serde( + serialize_with = "se::serialize_digest", + deserialize_with = "de::deserialize_digest" + )] + pub digest: Digest, + /// The MAST artifact ([Program] or [Library]) of the package + #[serde(serialize_with = "se::serialize_mast", deserialize_with = "de::deserialize_mast")] + pub mast: MastArtifact, + /// The rodata segments required to be loaded in the advice provider before executing the code + /// in this package + pub rodata: Vec, + /// The package manifest, containing the set of exported procedures and their signatures, + /// if known. + pub manifest: PackageManifest, +} + +impl Package { + const MAGIC: &'static [u8] = b"MASP\0"; + const FORMAT_VERSION: &'static [u8] = b"1.0\0"; + + /// Parses a package from the provided bytes + pub fn read_from_bytes(bytes: B) -> Result + where + B: AsRef<[u8]>, + { + use alloc::borrow::Cow; + + let bytes = bytes.as_ref(); + + let bytes = bytes + .strip_prefix(Self::MAGIC) + .ok_or_else(|| Report::msg("invalid package: missing header"))?; + let bytes = bytes.strip_prefix(Self::FORMAT_VERSION).ok_or_else(|| { + Report::msg(format!( + "invalid package: incorrect version, expected '1.0', got '{}'", + bytes.get(0..4).map(String::from_utf8_lossy).unwrap_or(Cow::Borrowed("")), + )) + })?; + + bitcode::deserialize(bytes).map_err(Report::msg) + } + + /// Serializes the package into a byte array + pub fn write_to_bytes(&self) -> Result, Report> { + let mut bytes = Vec::new(); + bytes.extend_from_slice(Self::MAGIC); + bytes.extend_from_slice(Self::FORMAT_VERSION); + let mut data = bitcode::serialize(self).map_err(Report::msg)?; + bytes.append(&mut data); + Ok(bytes) + } + + /// Checks if the package's MAST artifact is a [Program] + pub fn is_program(&self) -> bool { + matches!(self.mast, MastArtifact::Executable(_)) + } + + /// Checks if the package's MAST artifact is a [Library] + pub fn is_library(&self) -> bool { + matches!(self.mast, MastArtifact::Library(_)) + } + + /// Unwraps the package's MAST artifact as a [Program] or panics if it is a [Library] + pub fn unwrap_program(&self) -> Arc { + match self.mast { + MastArtifact::Executable(ref prog) => Arc::clone(prog), + _ => panic!("expected package to contain a program, but got a library"), + } + } + + /// Unwraps the package's MAST artifact as a [Library] or panics if it is a [Program] + pub fn unwrap_library(&self) -> Arc { + match self.mast { + MastArtifact::Library(ref lib) => Arc::clone(lib), + _ => panic!("expected package to contain a library, but got an executable"), + } + } + + /// Creates a new package with [Program] from this [Library] package and the given + /// entrypoint (should be a procedure in the library). + pub fn make_executable(&self, entrypoint: &QualifiedProcedureName) -> Result { + let MastArtifact::Library(ref library) = self.mast else { + return Err(Report::msg("expected library but got an executable")); + }; + + let module = library + .module_infos() + .find(|info| info.path() == &entrypoint.module) + .ok_or_else(|| { + Report::msg(format!( + "invalid entrypoint: library does not contain a module named '{}'", + entrypoint.module + )) + })?; + if let Some(digest) = module.get_procedure_digest_by_name(&entrypoint.name) { + let node_id = library.mast_forest().find_procedure_root(digest).ok_or_else(|| { + Report::msg( + "invalid entrypoint: malformed library - procedure exported, but digest has \ + no node in the forest", + ) + })?; + + let exports = BTreeSet::from_iter(self.manifest.exports.iter().find_map(|export| { + if export.digest == digest { + Some(export.clone()) + } else { + None + } + })); + + Ok(Self { + name: self.name.clone(), + digest, + mast: MastArtifact::Executable(Arc::new(Program::new( + library.mast_forest().clone(), + node_id, + ))), + rodata: self.rodata.clone(), + manifest: PackageManifest { + exports, + dependencies: self.manifest.dependencies.clone(), + }, + }) + } else { + Err(Report::msg(format!( + "invalid entrypoint: library does not export '{}'", + entrypoint + ))) + } + } + + /// The advice map that is expected to be loaded into the advice provider so that the + /// program's compiler-generated prologue will load the rodata from the advice provider into + /// memory before the program is executed + pub fn advice_map(&self) -> BTreeMap> { + let mut advice_inputs = BTreeMap::default(); + for rodata in self.rodata.iter() { + advice_inputs.extend([(rodata.digest, rodata.to_elements())]); + } + advice_inputs + } +} diff --git a/package/src/rodata.rs b/package/src/rodata.rs new file mode 100644 index 000000000..fdb5d3d42 --- /dev/null +++ b/package/src/rodata.rs @@ -0,0 +1,67 @@ +use alloc::vec::Vec; + +use serde::{Deserialize, Serialize}; +use vm_core::{utils::ToElements, Felt, FieldElement}; + +use super::{de, se}; +use crate::Digest; + +/// This represents a descriptor for a pointer referencing data in Miden's linear memory. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PtrDesc { + /// This is the address of the word containing the first byte of data + pub waddr: u32, + /// This is the element index of the word referenced by `waddr` containing the first byte of + /// data + /// + /// Each element is assumed to be a 32-bit value/chunk + pub index: u8, + /// This is the byte offset into the 32-bit chunk referenced by `index` + /// + /// This offset is where the data referenced by the pointer actually starts. + pub offset: u8, +} + +/// Represents a read-only data segment, combined with its content digest +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Rodata { + /// The content digest computed for `data` + #[serde( + serialize_with = "se::serialize_digest", + deserialize_with = "de::deserialize_digest" + )] + pub digest: Digest, + /// The address at which the data for this segment begins + pub start: PtrDesc, + /// The raw binary data for this segment + pub data: Vec, +} + +impl Rodata { + /// Returns the size of the data in bytes + pub fn size_in_bytes(&self) -> usize { + self.data.len() + } + + /// Returns the size of the data in felts + pub fn size_in_felts(&self) -> usize { + self.data.len().next_multiple_of(4) / 4 + } + + /// Returns the size of the data in VM words + pub fn size_in_words(&self) -> usize { + self.size_in_felts().next_multiple_of(4) / 4 + } + + /// Converts this rodata object to its equivalent representation in felts + /// + /// The resulting felts will be in padded out to the nearest number of words, i.e. if the data + /// only takes up 3 felts worth of bytes, then the resulting `Vec` will contain 4 felts, so that + /// the total size is a valid number of words. + pub fn to_elements(&self) -> Vec { + let mut felts = self.data.as_slice().to_elements(); + let padding = (self.size_in_words() * 4).abs_diff(felts.len()); + felts.resize(felts.len() + padding, Felt::ZERO); + felts + } +} diff --git a/package/src/se.rs b/package/src/se.rs new file mode 100644 index 000000000..91dba3de7 --- /dev/null +++ b/package/src/se.rs @@ -0,0 +1,33 @@ +use alloc::vec; + +use vm_core::utils::Serializable; + +use crate::{package::MastArtifact, Digest}; + +/// Serialize a [Digest] into a byte array +pub fn serialize_digest(digest: &Digest, serializer: S) -> Result +where + S: serde::Serializer, +{ + serde_bytes::serialize(&digest.as_bytes(), serializer) +} + +/// Serialize a [MastArtifact] into a byte array +pub fn serialize_mast(mast: &MastArtifact, serializer: S) -> Result +where + S: serde::Serializer, +{ + let mut buffer = vec![]; + match mast { + MastArtifact::Executable(program) => { + buffer.extend(b"PRG\0"); + program.write_into(&mut buffer); + }, + MastArtifact::Library(library) => { + buffer.extend(b"LIB\0"); + library.write_into(&mut buffer); + }, + } + + serde_bytes::serialize(&buffer, serializer) +} diff --git a/package/src/tests.rs b/package/src/tests.rs new file mode 100644 index 000000000..325e63bdb --- /dev/null +++ b/package/src/tests.rs @@ -0,0 +1,150 @@ +use std::{dbg, sync::Arc}; + +use assembly::Report; + +use super::*; + +#[ignore = "not implemented"] +#[test] +fn packaging_serialization() { + let package = example_package().unwrap(); + bitcode::serialize(package.as_ref()).unwrap(); + todo!("do it via roundtrip in proptest") +} + +#[ignore = "not implemented"] +#[test] +fn packaging_deserialization() -> Result<(), Report> { + let _expected = example_package()?; + todo!("do it via roundtrip in proptest") + + // let mut bytes = vec![]; + // expected + // .write_to(&mut bytes, midenc_session::OutputMode::Binary, &context.session) + // .into_diagnostic()?; + + // let package = Package::read_from_bytes(bytes)?; + + // assert_eq!(package.name, expected.name); + // assert_eq!(package.digest, expected.digest); + // assert_eq!(package.rodata, expected.rodata); + // assert_eq!(package.manifest, expected.manifest); + // assert!(package.is_program()); + + // // Verify rodata serialization + // assert!(!package.rodata.is_empty()); + // let expected_rodata_offset = PtrDesc::from_ptr(65536 * 4); + // let foo_data = package + // .rodata + // .iter() + // .find(|rodata| rodata.start == expected_rodata_offset) + // .unwrap(); + // let foo_bytes = foo_data.data.as_slice(); + + // let foo_ty = StructType::new([Type::U8, Type::U32, Type::U64]); + // let offset_u8 = foo_ty.get(0).offset as usize; + // let offset_u32 = foo_ty.get(1).offset as usize; + // let offset_u64 = foo_ty.get(2).offset as usize; + // assert_eq!(foo_bytes[offset_u8], 1); + // assert_eq!( + // u32::from_be_bytes([ + // foo_bytes[offset_u32], + // foo_bytes[offset_u32 + 1], + // foo_bytes[offset_u32 + 2], + // foo_bytes[offset_u32 + 3] + // ]), + // 2 + // ); + // assert_eq!( + // u32::from_be_bytes([ + // foo_bytes[offset_u64], + // foo_bytes[offset_u64 + 1], + // foo_bytes[offset_u64 + 2], + // foo_bytes[offset_u64 + 3] + // ]), + // 0 + // ); + // assert_eq!( + // u32::from_be_bytes([ + // foo_bytes[offset_u64 + 4], + // foo_bytes[offset_u64 + 5], + // foo_bytes[offset_u64 + 6], + // foo_bytes[offset_u64 + 7] + // ]), + // 3 + // ); + + // // Verify the MAST + // let expected = expected.unwrap_program(); + // let program = package.unwrap_program(); + // assert_eq!(program.hash(), expected.hash()); + // assert_eq!(program.mast_forest(), expected.mast_forest()); + + // Ok(()) +} + +fn example_package() -> Result, Report> { + todo!() + // use midenc_hir::ProgramBuilder; + + // // Build a simple program + // let mut builder = ProgramBuilder::new(&context.session.diagnostics); + + // // Build test module with fib function + // let mut mb = builder.module("test"); + // midenc_hir::testing::fib1(mb.as_mut(), context); + + // // Ensure we have an example data segment or two to work with + // let foo_ty = StructType::new([Type::U8, Type::U32, Type::U64]); + // // Initialize the struct with some data + // let offset_u8 = foo_ty.get(0).offset as usize; + // let offset_u32 = foo_ty.get(1).offset as usize; + // let offset_u64 = foo_ty.get(2).offset as usize; + // let foo_ty = Type::Struct(foo_ty); + // let foo_size = foo_ty.size_in_bytes(); + // let mut data = vec![0u8; foo_size]; + // unsafe { + // let data_ptr_range = data.as_mut_ptr_range(); + // core::ptr::write(data_ptr_range.start.byte_add(offset_u8), 1u8); + // core::ptr::write(data_ptr_range.start.byte_add(offset_u32).cast(), + // 2u32.to_be_bytes()); core::ptr::write(data_ptr_range.start.byte_add(offset_u64). + // cast(), 0u32.to_be_bytes()); // hi bits core::ptr::write(data_ptr_range.start. + // byte_add(offset_u64 + 4).cast(), 3u32.to_be_bytes()); // lo bits + // } + // mb.declare_data_segment(65536 * 4, foo_size as u32, data, true)?; + + // mb.build().expect("unexpected error constructing test module"); + + // // Link the program + // let mut program = builder + // .with_entrypoint("test::fib".parse().unwrap()) + // .link() + // .expect("failed to link program"); + + // program.add_library(StdLibrary::default().into()); + + // // Compile the program + // let mut compiler = crate::MasmCompiler::new(&context.session); + // let program = compiler.compile(program).expect("compilation failed").unwrap_executable(); + + // // Assemble the program + // let masm_artifact = MasmArtifact::Executable(program); + // let mast_artifact = masm_artifact.assemble(&context.session)?; + + // // Package the program + // Ok(Arc::new(Package::new(mast_artifact, &masm_artifact, &context.session))) +} + +#[ignore = "update the binary atfer the changes in the Miden package format are settled"] +#[test] +fn basic_wallet_package_deserialization() { + // Test for the https://github.com/0xPolygonMiden/compiler/issues/347 + // The included Miden package file is built at + // https://github.com/0xPolygonMiden/compiler/blob/6cd29e17b34c5abef7f6328c33af06f8bf203344/tests/integration/src/rust_masm_tests/rust_sdk.rs#L48-L63 + + let bytes = include_bytes!("../tests/data/basic_wallet.masp"); + + let package = Package::read_from_bytes(bytes).unwrap(); + dbg!(&package.manifest); + assert_eq!(package.name, "basic_wallet"); +} diff --git a/package/tests/data/basic_wallet.masp b/package/tests/data/basic_wallet.masp new file mode 100644 index 000000000..84f2b7555 Binary files /dev/null and b/package/tests/data/basic_wallet.masp differ diff --git a/processor/Cargo.toml b/processor/Cargo.toml index 3f8061c30..86b137099 100644 --- a/processor/Cargo.toml +++ b/processor/Cargo.toml @@ -28,6 +28,7 @@ miden-air = { package = "miden-air", path = "../air", version = "0.11", default- tracing = { version = "0.1", default-features = false, features = ["attributes"] } vm-core = { package = "miden-core", path = "../core", version = "0.11", default-features = false } winter-prover = { package = "winter-prover", version = "0.10", default-features = false } +package = { package = "miden-package", path = "../package", version = "0.11", default-features = false } [dev-dependencies] assembly = { package = "miden-assembly", path = "../assembly", version = "0.11", default-features = false }