Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(eof): avoid some allocations #1632

Merged
merged 2 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
/// Ethereum Object Format (EOF) container.
rakita marked this conversation as resolved.
Show resolved Hide resolved
///
/// 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
Loading