Skip to content

Commit

Permalink
perf(eof): avoid some allocations (#1632)
Browse files Browse the repository at this point in the history
* perf(eof): avoid some allocations

* Update crates/primitives/src/bytecode/eof.rs
  • Loading branch information
DaniPopes authored Jul 17, 2024
1 parent 782c3dd commit bac2f4a
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 54 deletions.
61 changes: 33 additions & 28 deletions crates/interpreter/src/interpreter/analysis.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -66,33 +65,36 @@ fn analyze(code: &[u8]) -> JumpTable {
JumpTable(Arc::new(jumps))
}

pub fn validate_raw_eof(bytecode: Bytes) -> Result<Eof, EofError> {
let eof = Eof::decode(bytecode)?;
/// Decodes `raw` into an [`Eof`] container and validates it.
pub fn validate_raw_eof(raw: Bytes) -> Result<Eof, EofError> {
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);
}
Expand All @@ -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)
Expand All @@ -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,
Expand All @@ -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);
}

Expand Down
34 changes: 14 additions & 20 deletions crates/primitives/src/bytecode/eof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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,
Expand All @@ -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()
Expand Down Expand Up @@ -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.
Expand Down
14 changes: 8 additions & 6 deletions crates/primitives/src/bytecode/eof/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -46,7 +48,7 @@ impl EofBody {
}
}

/// Encode Body into buffer.
/// Encodes this body into the given buffer.
pub fn encode(&self, buffer: &mut Vec<u8>) {
for types_section in &self.types_section {
types_section.encode(buffer);
Expand All @@ -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<Self, EofDecodeError> {
let header_len = header.size();
let partial_body_len =
Expand Down

0 comments on commit bac2f4a

Please sign in to comment.