From bac2f4ae22376e9fef7b53b81ace204bc06776c3 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Wed, 17 Jul 2024 17:28:45 +0300 Subject: [PATCH] perf(eof): avoid some allocations (#1632) * perf(eof): avoid some allocations * Update crates/primitives/src/bytecode/eof.rs --- .../interpreter/src/interpreter/analysis.rs | 61 ++++++++++--------- crates/primitives/src/bytecode/eof.rs | 34 +++++------ crates/primitives/src/bytecode/eof/body.rs | 14 +++-- 3 files changed, 55 insertions(+), 54 deletions(-) diff --git a/crates/interpreter/src/interpreter/analysis.rs b/crates/interpreter/src/interpreter/analysis.rs index 73d33d090d..def92c9ab3 100644 --- a/crates/interpreter/src/interpreter/analysis.rs +++ b/crates/interpreter/src/interpreter/analysis.rs @@ -1,17 +1,16 @@ -use revm_primitives::{eof::EofDecodeError, HashSet}; - use crate::{ instructions::utility::{read_i16, read_u16}, opcode, primitives::{ bitvec::prelude::{bitvec, BitVec, Lsb0}, - eof::TypesSection, + eof::{EofDecodeError, TypesSection}, legacy::JumpTable, - Bytecode, Bytes, Eof, LegacyAnalyzedBytecode, + Bytecode, Bytes, Eof, HashSet, LegacyAnalyzedBytecode, }, OPCODE_INFO_JUMPTABLE, STACK_LIMIT, }; -use std::{sync::Arc, vec, vec::Vec}; +use core::convert::identity; +use std::{borrow::Cow, sync::Arc, vec, vec::Vec}; const EOF_NON_RETURNING_FUNCTION: u8 = 0x80; @@ -66,33 +65,36 @@ fn analyze(code: &[u8]) -> JumpTable { JumpTable(Arc::new(jumps)) } -pub fn validate_raw_eof(bytecode: Bytes) -> Result { - let eof = Eof::decode(bytecode)?; +/// Decodes `raw` into an [`Eof`] container and validates it. +pub fn validate_raw_eof(raw: Bytes) -> Result { + let eof = Eof::decode(raw)?; validate_eof(&eof)?; Ok(eof) } -/// Validate Eof structures. +/// Fully validates an [`Eof`] container. pub fn validate_eof(eof: &Eof) -> Result<(), EofError> { - // clone is cheap as it is Bytes and a header. - let mut queue = vec![eof.clone()]; + if eof.body.container_section.is_empty() { + validate_eof_codes(eof)?; + return Ok(()); + } - while let Some(eof) = queue.pop() { - // iterate over types + let mut stack = Vec::with_capacity(4); + stack.push(Cow::Borrowed(eof)); + while let Some(eof) = stack.pop() { + // Validate the current container. validate_eof_codes(&eof)?; - // iterate over containers, convert them to Eof and add to analyze_eof - for container in eof.body.container_section { - queue.push(Eof::decode(container)?); + // Decode subcontainers and push them to the stack. + for container in &eof.body.container_section { + stack.push(Cow::Owned(Eof::decode(container.clone())?)); } } - // Eof is valid Ok(()) } -/// Validate EOF +/// Validates an [`Eof`] structure, without recursing into containers. pub fn validate_eof_codes(eof: &Eof) -> Result<(), EofValidationError> { - let mut queued_codes = vec![false; eof.body.code_section.len()]; if eof.body.code_section.len() != eof.body.types_section.len() { return Err(EofValidationError::InvalidTypesSection); } @@ -101,8 +103,6 @@ pub fn validate_eof_codes(eof: &Eof) -> Result<(), EofValidationError> { // no code sections. This should be already checked in decode. return Err(EofValidationError::NoCodeSections); } - // first section is default one. - queued_codes[0] = true; // the first code section must have a type signature // (0, 0x80, max_stack_height) (0 inputs non-returning function) @@ -111,11 +111,16 @@ pub fn validate_eof_codes(eof: &Eof) -> Result<(), EofValidationError> { return Err(EofValidationError::InvalidTypesSection); } + // first section is default one. + let mut code_sections_accessed = vec![false; eof.body.code_section.len()]; + code_sections_accessed[0] = true; + // start validation from code section 0. - let mut queue = vec![0]; - while let Some(index) = queue.pop() { + let mut stack = Vec::with_capacity(16); + stack.push(0); + while let Some(index) = stack.pop() { let code = &eof.body.code_section[index]; - let accessed_codes = validate_eof_code( + let accessed = validate_eof_code( code, eof.header.data_size as usize, index, @@ -124,15 +129,15 @@ pub fn validate_eof_codes(eof: &Eof) -> Result<(), EofValidationError> { )?; // queue accessed codes. - accessed_codes.into_iter().for_each(|i| { - if !queued_codes[i] { - queued_codes[i] = true; - queue.push(i); + accessed.into_iter().for_each(|i| { + if !code_sections_accessed[i] { + code_sections_accessed[i] = true; + stack.push(i); } }); } // iterate over accessed codes and check if all are accessed. - if queued_codes.into_iter().any(|x| !x) { + if !code_sections_accessed.into_iter().all(identity) { return Err(EofValidationError::CodeSectionNotAccessed); } diff --git a/crates/primitives/src/bytecode/eof.rs b/crates/primitives/src/bytecode/eof.rs index 1e60120e6e..17f0a41d97 100644 --- a/crates/primitives/src/bytecode/eof.rs +++ b/crates/primitives/src/bytecode/eof.rs @@ -21,13 +21,9 @@ pub const EOF_MAGIC: u16 = 0xEF00; /// EOF magic number in array form. pub static EOF_MAGIC_BYTES: Bytes = bytes!("ef00"); -/// EOF - Ethereum Object Format. +/// EVM Object Format (EOF) container. /// -/// It consist of a header, body and raw original bytes Specified in EIP. -/// Most of body contain Bytes so it references to the raw bytes. -/// -/// If there is a need to create new EOF from scratch, it is recommended to use `EofBody` and -/// use `encode` function to create full [`Eof`] object. +/// It consists of a header, body and the raw original bytes. #[derive(Clone, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Eof { @@ -42,7 +38,7 @@ impl Default for Eof { // types section with zero inputs, zero outputs and zero max stack size. types_section: vec![TypesSection::default()], // One code section with a STOP byte. - code_section: vec![[0x00].into()], + code_section: vec![Bytes::from_static(&[0x00])], container_section: vec![], data_section: Bytes::new(), is_data_filled: true, @@ -52,6 +48,11 @@ impl Default for Eof { } impl Eof { + /// Creates a new EOF container from the given body. + pub fn new(body: EofBody) -> Self { + body.into_eof() + } + /// Returns len of the header and body in bytes. pub fn size(&self) -> usize { self.header.size() + self.header.body_size() @@ -88,22 +89,15 @@ impl Eof { /// Decode EOF that have additional dangling bytes. /// Assume that data section is fully filled. - pub fn decode_dangling(mut eof: Bytes) -> Result<(Self, Bytes), EofDecodeError> { - let (header, _) = EofHeader::decode(&eof)?; + pub fn decode_dangling(mut raw: Bytes) -> Result<(Self, Bytes), EofDecodeError> { + let (header, _) = EofHeader::decode(&raw)?; let eof_size = header.body_size() + header.size(); - if eof_size > eof.len() { + if eof_size > raw.len() { return Err(EofDecodeError::MissingInput); } - let dangling_data = eof.split_off(eof_size); - let body = EofBody::decode(&eof, &header)?; - Ok(( - Self { - header, - body, - raw: eof, - }, - dangling_data, - )) + let dangling_data = raw.split_off(eof_size); + let body = EofBody::decode(&raw, &header)?; + Ok((Self { header, body, raw }, dangling_data)) } /// Decode EOF from raw bytes. diff --git a/crates/primitives/src/bytecode/eof/body.rs b/crates/primitives/src/bytecode/eof/body.rs index 3759372b8a..e4e9ab904c 100644 --- a/crates/primitives/src/bytecode/eof/body.rs +++ b/crates/primitives/src/bytecode/eof/body.rs @@ -2,9 +2,11 @@ use super::{Eof, EofDecodeError, EofHeader, TypesSection}; use crate::Bytes; use std::vec::Vec; -/// EOF Body, contains types, code, container and data sections. +/// EOF container body. /// -/// Can be used to create new EOF object. +/// Contains types, code, container and data sections. +/// +/// Can be used to create a new EOF container using the [`into_eof`](EofBody::into_eof) method. #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct EofBody { @@ -16,12 +18,12 @@ pub struct EofBody { } impl EofBody { - // Get code section + /// Returns the code section at the given index. pub fn code(&self, index: usize) -> Option<&Bytes> { self.code_section.get(index) } - /// Create EOF from body. + /// Creates an EOF container from this body. pub fn into_eof(self) -> Eof { // TODO add bounds checks. let header = EofHeader { @@ -46,7 +48,7 @@ impl EofBody { } } - /// Encode Body into buffer. + /// Encodes this body into the given buffer. pub fn encode(&self, buffer: &mut Vec) { for types_section in &self.types_section { types_section.encode(buffer); @@ -63,7 +65,7 @@ impl EofBody { buffer.extend_from_slice(&self.data_section); } - /// Decode EOF body from buffer and Header. + /// Decodes an EOF container body from the given buffer and header. pub fn decode(input: &Bytes, header: &EofHeader) -> Result { let header_len = header.size(); let partial_body_len =