From afbd43c163297aa91e3afe697d19292390365ca4 Mon Sep 17 00:00:00 2001 From: Bobbin Threadbare <43513081+bobbinth@users.noreply.github.com> Date: Tue, 30 Jul 2024 09:20:57 -0700 Subject: [PATCH] feat: implement kernel compilation in the assembler (#1418) --- CHANGELOG.md | 10 ++- assembly/src/assembler/id.rs | 11 +++ assembly/src/assembler/mod.rs | 94 +++++++++++++--------- assembly/src/assembler/module_graph/mod.rs | 35 ++++++-- assembly/src/assembler/tests.rs | 8 +- assembly/src/ast/module.rs | 25 +++++- assembly/src/errors.rs | 55 ++----------- assembly/src/lib.rs | 1 - assembly/src/library/error.rs | 11 ++- assembly/src/library/mod.rs | 76 ++++++++++++++++- assembly/src/tests.rs | 4 +- core/src/kernel.rs | 24 +++--- 12 files changed, 230 insertions(+), 124 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fa7a5cedc..e5e5b77317 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,15 +15,17 @@ - Relaxed the parser to allow one branch of an `if.(true|false)` to be empty - Added support for immediate values for `u32and`, `u32or`, `u32xor` and `u32not` bitwise instructions (#1362). - Optimized `std::sys::truncate_stuck` procedure (#1384). -- Updated CI and Makefile to standardise it accross Miden repositories (#1342). +- Updated CI and Makefile to standardize it across Miden repositories (#1342). - Add serialization/deserialization for `MastForest` (#1370) - Assembler: add the ability to compile MAST libraries, and to assemble a program using compiled libraries (#1401) - Updated CI to support `CHANGELOG.md` modification checking and `no changelog` label (#1406) - Introduced `MastForestError` to enforce `MastForest` node count invariant (#1394) -- Added functions to `MastForest` and `MastForestBuilder` to add and ensure nodes with fewer LOC (#1404, #1412) -- Made `Assembler` single-use (#1409) +- Added functions to `MastForestBuilder` to allow ensuring of nodes with fewer LOC (#1404) +- [BREAKING] Made `Assembler` single-use (#1409). - Removed `ProcedureCache` from the assembler (#1411). -- Added `Assembler::assemble_library()` (#1413) +- Added functions to `MastForest` and `MastForestBuilder` to add and ensure nodes with fewer LOC (#1404, #1412) +- Added `Assembler::assemble_library()` (#1413). +- Added `Assembler::assemble_kernel()` (#1418). #### Changed diff --git a/assembly/src/assembler/id.rs b/assembly/src/assembler/id.rs index b7cfd1348a..7a9944363a 100644 --- a/assembly/src/assembler/id.rs +++ b/assembly/src/assembler/id.rs @@ -54,3 +54,14 @@ impl ModuleIndex { self.0 as usize } } + +impl core::ops::Add for ModuleIndex { + type Output = GlobalProcedureIndex; + + fn add(self, rhs: ProcedureIndex) -> Self::Output { + GlobalProcedureIndex { + module: self, + index: rhs, + } + } +} diff --git a/assembly/src/assembler/mod.rs b/assembly/src/assembler/mod.rs index 88dc369577..ba0bd395d4 100644 --- a/assembly/src/assembler/mod.rs +++ b/assembly/src/assembler/mod.rs @@ -1,9 +1,7 @@ use crate::{ - ast::{ - self, FullyQualifiedProcedureName, InvocationTarget, InvokeKind, ModuleKind, ProcedureIndex, - }, + ast::{self, FullyQualifiedProcedureName, InvocationTarget, InvokeKind, ModuleKind}, diagnostics::Report, - library::CompiledLibrary, + library::{CompiledLibrary, KernelLibrary}, sema::SemanticAnalysisError, AssemblyError, Compile, CompileOptions, Library, LibraryNamespace, LibraryPath, RpoDigest, Spanned, @@ -62,18 +60,13 @@ pub struct Assembler { // ------------------------------------------------------------------------------------------------ /// Constructors impl Assembler { - /// Start building an [Assembler] - pub fn new() -> Self { - Self::default() - } - - /// Start building an [`Assembler`] with the given [`Kernel`]. - pub fn with_kernel(kernel: Kernel) -> Self { - let mut assembler = Self::new(); - - assembler.module_graph.set_kernel(None, kernel); - - assembler + /// Start building an [`Assembler`] with a kernel defined by the provided [KernelLibrary]. + pub fn with_kernel(kernel_lib: KernelLibrary) -> Self { + let (kernel, kernel_module, _) = kernel_lib.into_parts(); + Self { + module_graph: ModuleGraph::with_kernel(kernel, kernel_module), + ..Default::default() + } } /// Sets the default behavior of this assembler with regard to warning diagnostics. @@ -149,7 +142,8 @@ impl Assembler { pub fn add_compiled_library(&mut self, library: CompiledLibrary) -> Result<(), Report> { self.module_graph .add_compiled_modules(library.into_module_infos()) - .map_err(Report::from) + .map_err(Report::from)?; + Ok(()) } /// Adds the library to provide modules for the compilation. @@ -235,7 +229,11 @@ impl Assembler { // ------------------------------------------------------------------------------------------------ /// Compilation/Assembly impl Assembler { - /// Assembles a set of modules into a library. + /// Assembles a set of modules into a [CompiledLibrary]. + /// + /// # Errors + /// + /// Returns an error if parsing or compilation of the specified modules fails. pub fn assemble_library( mut self, modules: impl Iterator, @@ -254,29 +252,14 @@ impl Assembler { let exports = { let mut exports = Vec::new(); - for ast_module_idx in ast_module_indices { + for module_idx in ast_module_indices { // Note: it is safe to use `unwrap_ast()` here, since all modules looped over are // AST (we just added them to the module graph) - let ast_module = self.module_graph[ast_module_idx].unwrap_ast().clone(); - - for (proc_idx, procedure) in ast_module.procedures().enumerate() { - // Only add exports; locals will be added if they are in the call graph rooted - // at those procedures - if !procedure.visibility().is_exported() { - continue; - } - - let gid = GlobalProcedureIndex { - module: ast_module_idx, - index: ProcedureIndex::new(proc_idx), - }; + let ast_module = self.module_graph[module_idx].unwrap_ast().clone(); - self.compile_subgraph(gid, false, &mut mast_forest_builder)?; - - exports.push(FullyQualifiedProcedureName::new( - ast_module.path().clone(), - procedure.name().clone(), - )); + for (proc_idx, fqn) in ast_module.exported_procedures() { + self.compile_subgraph(module_idx + proc_idx, false, &mut mast_forest_builder)?; + exports.push(fqn); } } @@ -286,6 +269,41 @@ impl Assembler { Ok(CompiledLibrary::new(mast_forest_builder.build(), exports)?) } + /// Assembles the provided module into a [KernelLibrary] intended to be used as a Kernel. + /// + /// # Errors + /// + /// Returns an error if parsing or compilation of the specified modules fails. + pub fn assemble_kernel(mut self, module: impl Compile) -> Result { + let options = CompileOptions { + kind: ModuleKind::Kernel, + warnings_as_errors: self.warnings_as_errors, + path: Some(LibraryPath::from(LibraryNamespace::Kernel)), + }; + + let module = module.compile_with_options(options)?; + let module_idx = self.module_graph.add_ast_module(module)?; + + self.module_graph.recompute()?; + + let mut mast_forest_builder = MastForestBuilder::default(); + + // Note: it is safe to use `unwrap_ast()` here, since all modules looped over are + // AST (we just added them to the module graph) + let ast_module = self.module_graph[module_idx].unwrap_ast().clone(); + + let exports = ast_module + .exported_procedures() + .map(|(proc_idx, fqn)| { + self.compile_subgraph(module_idx + proc_idx, false, &mut mast_forest_builder)?; + Ok(fqn) + }) + .collect::, Report>>()?; + + let library = CompiledLibrary::new(mast_forest_builder.build(), exports)?; + Ok(library.try_into()?) + } + /// Compiles the provided module into a [`Program`]. The resulting program can be executed on /// Miden VM. /// diff --git a/assembly/src/assembler/module_graph/mod.rs b/assembly/src/assembler/module_graph/mod.rs index a5ea5a3e05..e0835f96f3 100644 --- a/assembly/src/assembler/module_graph/mod.rs +++ b/assembly/src/assembler/module_graph/mod.rs @@ -21,9 +21,12 @@ use crate::{ ProcedureName, ResolvedProcedure, }, library::{ModuleInfo, ProcedureInfo}, - AssemblyError, LibraryPath, RpoDigest, Spanned, + AssemblyError, LibraryNamespace, LibraryPath, RpoDigest, Spanned, }; +// WRAPPER STRUCTS +// ================================================================================================ + /// Wraps all supported representations of a procedure in the module graph. /// /// Currently, there are two supported representations: @@ -168,7 +171,7 @@ impl ModuleGraph { pub fn add_compiled_modules( &mut self, module_infos: impl Iterator, - ) -> Result<(), AssemblyError> { + ) -> Result, AssemblyError> { let module_indices: Vec = module_infos .map(|module| self.add_module(PendingWrappedModule::Info(module))) .collect::>()?; @@ -176,7 +179,7 @@ impl ModuleGraph { self.recompute()?; // Register all procedures as roots - for module_index in module_indices { + for &module_index in module_indices.iter() { for (proc_index, proc) in self[module_index].unwrap_info().clone().procedure_infos() { let gid = GlobalProcedureIndex { module: module_index, @@ -187,7 +190,7 @@ impl ModuleGraph { } } - Ok(()) + Ok(module_indices) } /// Add `module` to the graph. @@ -238,9 +241,27 @@ impl ModuleGraph { // ------------------------------------------------------------------------------------------------ /// Kernels impl ModuleGraph { - pub(super) fn set_kernel(&mut self, kernel_index: Option, kernel: Kernel) { - self.kernel_index = kernel_index; - self.kernel = kernel; + /// Returns a new [ModuleGraph] instantiated from the provided kernel and kernel info module. + /// + /// Note: it is assumed that kernel and kernel_module are consistent, but this is not checked. + /// + /// TODO: consider passing `KerneLibrary` into this constructor as a parameter instead. + pub(super) fn with_kernel(kernel: Kernel, kernel_module: ModuleInfo) -> Self { + assert!(!kernel.is_empty()); + assert_eq!(kernel_module.path(), &LibraryPath::from(LibraryNamespace::Kernel)); + + // add kernel module to the graph + // TODO: simplify this to avoid using Self::add_compiled_modules() + let mut graph = Self::default(); + let module_indexes = graph + .add_compiled_modules([kernel_module].into_iter()) + .expect("failed to add kernel module to the module graph"); + assert_eq!(module_indexes[0], ModuleIndex::new(0), "kernel should be the first module"); + + graph.kernel_index = Some(module_indexes[0]); + graph.kernel = kernel; + + graph } pub fn kernel(&self) -> &Kernel { diff --git a/assembly/src/assembler/tests.rs b/assembly/src/assembler/tests.rs index 18a8d58ff2..cfec29207d 100644 --- a/assembly/src/assembler/tests.rs +++ b/assembly/src/assembler/tests.rs @@ -66,7 +66,7 @@ fn nested_blocks() { } } - let assembler = Assembler::new().with_library(&DummyLibrary::default()).unwrap(); + let assembler = Assembler::default().with_library(&DummyLibrary::default()).unwrap(); // The expected `MastForest` for the program (that we will build by hand) let mut expected_mast_forest_builder = MastForestBuilder::default(); @@ -200,7 +200,7 @@ fn nested_blocks() { /// forest. #[test] fn duplicate_procedure() { - let assembler = Assembler::new(); + let assembler = Assembler::default(); let program_source = r#" proc.foo @@ -227,7 +227,7 @@ fn duplicate_procedure() { /// Ensures that equal MAST nodes don't get added twice to a MAST forest #[test] fn duplicate_nodes() { - let assembler = Assembler::new(); + let assembler = Assembler::default(); let program_source = r#" begin @@ -317,7 +317,7 @@ fn explicit_fully_qualified_procedure_references() { } } - let assembler = Assembler::new().with_library(&DummyLibrary::default()).unwrap(); + let assembler = Assembler::default().with_library(&DummyLibrary::default()).unwrap(); let program = r#" begin diff --git a/assembly/src/ast/module.rs b/assembly/src/ast/module.rs index 1c608fa04a..bf31df4f24 100644 --- a/assembly/src/ast/module.rs +++ b/assembly/src/ast/module.rs @@ -6,7 +6,10 @@ use alloc::{ }; use core::fmt; -use super::{Export, Import, LocalNameResolver, ProcedureIndex, ProcedureName, ResolvedProcedure}; +use super::{ + Export, FullyQualifiedProcedureName, Import, LocalNameResolver, ProcedureIndex, ProcedureName, + ResolvedProcedure, +}; use crate::{ ast::{AliasTarget, AstSerdeOptions, Ident}, diagnostics::{Report, SourceFile}, @@ -365,6 +368,26 @@ impl Module { self.procedures.iter_mut() } + /// Returns procedures exported from this module. + /// + /// Each exported procedure is represented by its local procedure index and a fully qualified + /// name. + pub fn exported_procedures( + &self, + ) -> impl Iterator + '_ { + self.procedures.iter().enumerate().filter_map(|(proc_idx, p)| { + // skip un-exported procedures + if !p.visibility().is_exported() { + return None; + } + + let proc_idx = ProcedureIndex::new(proc_idx); + let fqn = FullyQualifiedProcedureName::new(self.path().clone(), p.name().clone()); + + Some((proc_idx, fqn)) + }) + } + /// Get an iterator over the imports declared in this module. /// /// See [Import] for details on what information is available for imports. diff --git a/assembly/src/errors.rs b/assembly/src/errors.rs index 489bebef3d..d55d73bbec 100644 --- a/assembly/src/errors.rs +++ b/assembly/src/errors.rs @@ -2,11 +2,14 @@ use alloc::{string::String, sync::Arc, vec::Vec}; use vm_core::mast::MastForestError; use crate::{ - ast::{FullyQualifiedProcedureName, ProcedureName}, + ast::FullyQualifiedProcedureName, diagnostics::{Diagnostic, RelatedError, RelatedLabel, Report, SourceFile}, - KernelError, LibraryNamespace, LibraryPath, RpoDigest, SourceSpan, + LibraryNamespace, LibraryPath, RpoDigest, SourceSpan, }; +// ASSEMBLY ERROR +// ================================================================================================ + /// An error which can be generated while compiling a Miden assembly program into a MAST. #[derive(Debug, thiserror::Error, Diagnostic)] #[non_exhaustive] @@ -24,21 +27,12 @@ pub enum AssemblyError { #[error("found a cycle in the call graph, involving these procedures: {}", nodes.as_slice().join(", "))] #[diagnostic()] Cycle { nodes: Vec }, - #[error("attempted to provide multiple kernel modules to the assembler")] - #[diagnostic()] - ConflictingKernels, #[error("two procedures found with same mast root, but conflicting definitions ('{first}' and '{second}')")] #[diagnostic()] ConflictingDefinitions { first: FullyQualifiedProcedureName, second: FullyQualifiedProcedureName, }, - #[error("conflicting entrypoints were provided (in '{first}' and '{second}')")] - #[diagnostic()] - MultipleEntry { - first: LibraryPath, - second: LibraryPath, - }, #[error("duplicate definition found for module '{path}'")] #[diagnostic()] DuplicateModule { path: LibraryPath }, @@ -51,33 +45,12 @@ pub enum AssemblyError { source_file: Option>, path: LibraryPath, }, - #[error("undefined local procedure '{name}'")] - #[diagnostic()] - UndefinedLocalProcedure { - #[label] - name: ProcedureName, - #[source_code] - source_file: Option>, - }, - #[error("expected procedure with MAST root '{digest}' to be found in assembler cache, but it was not")] - #[diagnostic()] - UndefinedCallSetProcedure { digest: RpoDigest }, #[error("module namespace is inconsistent with library ('{actual}' vs '{expected}')")] #[diagnostic()] InconsistentNamespace { expected: LibraryNamespace, actual: LibraryNamespace, }, - #[error( - "re-exported procedure '{name}' is self-recursive: resolving the alias is not possible" - )] - #[diagnostic()] - RecursiveAlias { - #[label("recursion starts here")] - name: FullyQualifiedProcedureName, - #[source_code] - source_file: Option>, - }, #[error("invalid syscall: '{callee}' is not an exported kernel procedure")] #[diagnostic()] InvalidSysCallTarget { @@ -108,24 +81,6 @@ pub enum AssemblyError { }, #[error(transparent)] #[diagnostic(transparent)] - Parsing(#[from] crate::parser::ParsingError), - #[error(transparent)] - #[diagnostic(transparent)] - SemanticAnalysis(#[from] crate::sema::SemanticAnalysisError), - #[error(transparent)] - #[diagnostic(transparent)] - Syntax(#[from] crate::sema::SyntaxError), - #[error(transparent)] - #[diagnostic()] - Kernel(#[from] KernelError), - #[error(transparent)] - #[diagnostic(transparent)] - Library(#[from] crate::library::LibraryError), - #[error(transparent)] - #[cfg(feature = "std")] - Io(#[from] std::io::Error), - #[error(transparent)] - #[diagnostic(transparent)] Other(#[from] RelatedError), #[error(transparent)] Forest(#[from] MastForestError), diff --git a/assembly/src/lib.rs b/assembly/src/lib.rs index 0fccde3e2c..c0a3e124cf 100644 --- a/assembly/src/lib.rs +++ b/assembly/src/lib.rs @@ -9,7 +9,6 @@ extern crate std; use vm_core::{ crypto::hash::RpoDigest, - errors::KernelError, utils::{ ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, SliceReader, }, diff --git a/assembly/src/library/error.rs b/assembly/src/library/error.rs index fc6a803795..b8361d04b4 100644 --- a/assembly/src/library/error.rs +++ b/assembly/src/library/error.rs @@ -1,7 +1,10 @@ use alloc::string::String; +use vm_core::errors::KernelError; use crate::{ - diagnostics::Diagnostic, library::LibraryNamespaceError, library::VersionError, + ast::FullyQualifiedProcedureName, + diagnostics::Diagnostic, + library::{LibraryNamespaceError, VersionError}, DeserializationError, LibraryNamespace, LibraryPath, PathError, }; @@ -68,4 +71,10 @@ pub enum CompiledLibraryError { exports_len: usize, roots_len: usize, }, + #[error("Invalid export in kernel library: {procedure_path}")] + InvalidKernelExport { + procedure_path: FullyQualifiedProcedureName, + }, + #[error(transparent)] + Kernel(#[from] KernelError), } diff --git a/assembly/src/library/mod.rs b/assembly/src/library/mod.rs index 3bc7f5ae8e..fcaf60f6e4 100644 --- a/assembly/src/library/mod.rs +++ b/assembly/src/library/mod.rs @@ -2,6 +2,7 @@ use alloc::{collections::BTreeMap, vec::Vec}; use vm_core::crypto::hash::RpoDigest; use vm_core::mast::MastForest; +use vm_core::Kernel; use crate::ast::{self, FullyQualifiedProcedureName, ProcedureIndex, ProcedureName}; @@ -21,7 +22,7 @@ pub use self::version::{Version, VersionError}; mod tests; // COMPILED LIBRARY -// =============================================================================================== +// ================================================================================================ /// Represents a library where all modules modules were compiled into a [`MastForest`]. pub struct CompiledLibrary { @@ -94,6 +95,67 @@ impl CompiledLibrary { } } +// KERNEL LIBRARY +// ================================================================================================ + +/// Represents a library containing a Miden VM kernel. +/// +/// This differs from the regular [CompiledLibrary] as follows: +/// - All exported procedures must be exported directly from the kernel namespace (i.e., `#sys`). +/// - The number of exported procedures cannot exceed [Kernel::MAX_NUM_PROCEDURES] (i.e., 256). +pub struct KernelLibrary { + kernel: Kernel, + kernel_info: ModuleInfo, + library: CompiledLibrary, +} + +impl KernelLibrary { + /// Destructures this kernel library into individual parts. + pub fn into_parts(self) -> (Kernel, ModuleInfo, MastForest) { + (self.kernel, self.kernel_info, self.library.mast_forest) + } +} + +impl TryFrom for KernelLibrary { + type Error = CompiledLibraryError; + + fn try_from(library: CompiledLibrary) -> Result { + let kernel_path = LibraryPath::from(LibraryNamespace::Kernel); + let mut kernel_procs = Vec::with_capacity(library.exports.len()); + let mut proc_digests = Vec::with_capacity(library.exports.len()); + + for (proc_index, proc_path) in library.exports.iter().enumerate() { + // make sure all procedures are exported directly from the #sys module + if proc_path.module != kernel_path { + return Err(CompiledLibraryError::InvalidKernelExport { + procedure_path: proc_path.clone(), + }); + } + + let proc_node_id = library.mast_forest.procedure_roots()[proc_index]; + let proc_digest = library.mast_forest[proc_node_id].digest(); + + proc_digests.push(proc_digest); + kernel_procs.push(ProcedureInfo { + name: proc_path.name.clone(), + digest: proc_digest, + }); + } + + let kernel = Kernel::new(&proc_digests)?; + let module_info = ModuleInfo::new(kernel_path, kernel_procs); + + Ok(Self { + kernel, + kernel_info: module_info, + library, + }) + } +} + +// MODULE INFO +// ================================================================================================ + #[derive(Debug, Clone)] pub struct ModuleInfo { path: LibraryPath, @@ -101,8 +163,11 @@ pub struct ModuleInfo { } impl ModuleInfo { - /// Constructs a new [`ModuleInfo`]. - pub fn new(path: LibraryPath, procedures: Vec) -> Self { + /// Returns a new [`ModuleInfo`] instantiated from the provided procedures. + /// + /// Note: this constructor assumes that the fully-qualified names of the provided procedures + /// are consistent with the provided module path, but this is not checked. + fn new(path: LibraryPath, procedures: Vec) -> Self { Self { path, procedure_infos: procedures, @@ -133,6 +198,11 @@ impl ModuleInfo { .map(|(idx, proc)| (ProcedureIndex::new(idx), proc)) } + /// Returns an iterator over the MAST roots of procedures defined in this module. + pub fn procedure_digests(&self) -> impl Iterator + '_ { + self.procedure_infos.iter().map(|p| p.digest) + } + /// Returns the [`ProcedureInfo`] of the procedure at the provided index, if any. pub fn get_proc_info_by_index(&self, index: ProcedureIndex) -> Option<&ProcedureInfo> { self.procedure_infos.get(index.as_usize()) diff --git a/assembly/src/tests.rs b/assembly/src/tests.rs index d50a6484e0..02734bc1f7 100644 --- a/assembly/src/tests.rs +++ b/assembly/src/tests.rs @@ -2405,14 +2405,14 @@ fn test_compiled_library() { }; let compiled_library = { - let assembler = Assembler::new(); + let assembler = Assembler::default(); assembler.assemble_library(vec![mod1, mod2].into_iter()).unwrap() }; assert_eq!(compiled_library.exports().len(), 4); // Compile program that uses compiled library - let mut assembler = Assembler::new(); + let mut assembler = Assembler::default(); assembler.add_compiled_library(compiled_library).unwrap(); diff --git a/core/src/kernel.rs b/core/src/kernel.rs index 0f225822de..07da6ca3d6 100644 --- a/core/src/kernel.rs +++ b/core/src/kernel.rs @@ -5,6 +5,9 @@ use crate::{ use alloc::vec::Vec; use miden_crypto::hash::rpo::RpoDigest; +// KERNEL +// ================================================================================================ + /// A list of procedure hashes defining a VM kernel. /// /// The internally-stored list always has a consistent order, regardless of the order of procedure @@ -12,13 +15,14 @@ use miden_crypto::hash::rpo::RpoDigest; #[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct Kernel(Vec); -pub const MAX_KERNEL_PROCEDURES: usize = u8::MAX as usize; - impl Kernel { + /// The maximum number of procedures which can be exported from a Kernel. + pub const MAX_NUM_PROCEDURES: usize = u8::MAX as usize; + /// Returns a new [Kernel] instantiated with the specified procedure hashes. pub fn new(proc_hashes: &[RpoDigest]) -> Result { - if proc_hashes.len() > MAX_KERNEL_PROCEDURES { - Err(KernelError::TooManyProcedures(MAX_KERNEL_PROCEDURES, proc_hashes.len())) + if proc_hashes.len() > Self::MAX_NUM_PROCEDURES { + Err(KernelError::TooManyProcedures(Self::MAX_NUM_PROCEDURES, proc_hashes.len())) } else { let mut hashes = proc_hashes.to_vec(); hashes.sort_by_key(|v| v.as_bytes()); // ensure consistent order @@ -52,21 +56,15 @@ impl Kernel { // this is required by AIR as public inputs will be serialized with the proof impl Serializable for Kernel { fn write_into(&self, target: &mut W) { - debug_assert!(self.0.len() <= MAX_KERNEL_PROCEDURES); - target.write_usize(self.0.len()); + // expect is OK here because the number of procedures is enforced by the constructor + target.write_u8(self.0.len().try_into().expect("too many kernel procedures")); target.write_many(&self.0) } } impl Deserializable for Kernel { fn read_from(source: &mut R) -> Result { - let len = source.read_usize()?; - if len > MAX_KERNEL_PROCEDURES { - return Err(DeserializationError::InvalidValue(format!( - "Number of kernel procedures can not be more than {}, but {} was provided", - MAX_KERNEL_PROCEDURES, len - ))); - } + let len = source.read_u8()? as usize; let kernel = source.read_many::(len)?; Ok(Self(kernel)) }