mirror of
https://github.com/pezkuwichain/revive.git
synced 2026-04-28 01:38:05 +00:00
Emerge Yul recompiler (#1)
Provide a modified (and incomplete) version of ZKSync zksolc that can compile the most basic contracts
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
//!
|
||||
//! The inner JSON legacy assembly code element.
|
||||
//!
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::evmla::assembly::Assembly;
|
||||
|
||||
///
|
||||
/// The inner JSON legacy assembly code element.
|
||||
///
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
#[serde(untagged)]
|
||||
pub enum Data {
|
||||
/// The assembly code wrapper.
|
||||
Assembly(Assembly),
|
||||
/// The hash.
|
||||
Hash(String),
|
||||
/// The full contract path after the factory dependencies replacing pass.
|
||||
Path(String),
|
||||
}
|
||||
|
||||
impl Data {
|
||||
///
|
||||
/// Returns the inner assembly reference if it is present.
|
||||
///
|
||||
pub fn get_assembly(&self) -> Option<&Assembly> {
|
||||
match self {
|
||||
Self::Assembly(ref assembly) => Some(assembly),
|
||||
Self::Hash(_) => None,
|
||||
Self::Path(_) => None,
|
||||
}
|
||||
}
|
||||
///
|
||||
/// Returns the inner assembly mutable reference if it is present.
|
||||
///
|
||||
pub fn get_assembly_mut(&mut self) -> Option<&mut Assembly> {
|
||||
match self {
|
||||
Self::Assembly(ref mut assembly) => Some(assembly),
|
||||
Self::Hash(_) => None,
|
||||
Self::Path(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Get the list of missing deployable libraries.
|
||||
///
|
||||
pub fn get_missing_libraries(&self) -> HashSet<String> {
|
||||
match self {
|
||||
Self::Assembly(assembly) => assembly.get_missing_libraries(),
|
||||
Self::Hash(_) => HashSet::new(),
|
||||
Self::Path(_) => HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Gets the contract `keccak256` hash.
|
||||
///
|
||||
pub fn keccak256(&self) -> String {
|
||||
match self {
|
||||
Self::Assembly(assembly) => assembly.keccak256(),
|
||||
Self::Hash(hash) => panic!("Expected assembly, found hash `{hash}`"),
|
||||
Self::Path(path) => panic!("Expected assembly, found path `{path}`"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Data {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Assembly(inner) => writeln!(f, "{inner}"),
|
||||
Self::Hash(inner) => writeln!(f, "Hash `{inner}`"),
|
||||
Self::Path(inner) => writeln!(f, "Path `{inner}`"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
//!
|
||||
//! Translates the CODECOPY use cases.
|
||||
//!
|
||||
|
||||
///
|
||||
/// Translates the contract hash copying.
|
||||
///
|
||||
pub fn contract_hash<'ctx, D>(
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<'ctx, D>,
|
||||
offset: inkwell::values::IntValue<'ctx>,
|
||||
value: inkwell::values::IntValue<'ctx>,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
D: era_compiler_llvm_context::EraVMDependency + Clone,
|
||||
{
|
||||
let offset = context.builder().build_int_add(
|
||||
offset,
|
||||
context.field_const(
|
||||
(era_compiler_common::BYTE_LENGTH_X32 + era_compiler_common::BYTE_LENGTH_FIELD) as u64,
|
||||
),
|
||||
"datacopy_contract_hash_offset",
|
||||
)?;
|
||||
|
||||
era_compiler_llvm_context::eravm_evm_memory::store(context, offset, value)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Translates the library marker copying.
|
||||
///
|
||||
pub fn library_marker<D>(
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<D>,
|
||||
offset: u64,
|
||||
value: u64,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
D: era_compiler_llvm_context::EraVMDependency + Clone,
|
||||
{
|
||||
era_compiler_llvm_context::eravm_evm_memory::store_byte(
|
||||
context,
|
||||
context.field_const(offset),
|
||||
context.field_const(value),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Translates the static data copying.
|
||||
///
|
||||
pub fn static_data<'ctx, D>(
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<'ctx, D>,
|
||||
destination: inkwell::values::IntValue<'ctx>,
|
||||
source: &str,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
D: era_compiler_llvm_context::EraVMDependency + Clone,
|
||||
{
|
||||
let mut offset = 0;
|
||||
for (index, chunk) in source
|
||||
.chars()
|
||||
.collect::<Vec<char>>()
|
||||
.chunks(era_compiler_common::BYTE_LENGTH_FIELD * 2)
|
||||
.enumerate()
|
||||
{
|
||||
let mut value_string = chunk.iter().collect::<String>();
|
||||
value_string.push_str(
|
||||
"0".repeat((era_compiler_common::BYTE_LENGTH_FIELD * 2) - chunk.len())
|
||||
.as_str(),
|
||||
);
|
||||
|
||||
let datacopy_destination = context.builder().build_int_add(
|
||||
destination,
|
||||
context.field_const(offset as u64),
|
||||
format!("datacopy_destination_index_{index}").as_str(),
|
||||
)?;
|
||||
let datacopy_value = context.field_const_str_hex(value_string.as_str());
|
||||
era_compiler_llvm_context::eravm_evm_memory::store(
|
||||
context,
|
||||
datacopy_destination,
|
||||
datacopy_value,
|
||||
)?;
|
||||
offset += chunk.len() / 2;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
//!
|
||||
//! Translates the jump operations.
|
||||
//!
|
||||
|
||||
///
|
||||
/// Translates the unconditional jump.
|
||||
///
|
||||
pub fn unconditional<D>(
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<D>,
|
||||
destination: num::BigUint,
|
||||
stack_hash: md5::Digest,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
D: era_compiler_llvm_context::EraVMDependency + Clone,
|
||||
{
|
||||
let code_type = context
|
||||
.code_type()
|
||||
.ok_or_else(|| anyhow::anyhow!("The contract code part type is undefined"))?;
|
||||
let block_key = era_compiler_llvm_context::EraVMFunctionBlockKey::new(code_type, destination);
|
||||
|
||||
let block = context
|
||||
.current_function()
|
||||
.borrow()
|
||||
.evmla()
|
||||
.find_block(&block_key, &stack_hash)?;
|
||||
context.build_unconditional_branch(block.inner());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Translates the conditional jump.
|
||||
///
|
||||
pub fn conditional<D>(
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<D>,
|
||||
destination: num::BigUint,
|
||||
stack_hash: md5::Digest,
|
||||
stack_height: usize,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
D: era_compiler_llvm_context::EraVMDependency + Clone,
|
||||
{
|
||||
let code_type = context
|
||||
.code_type()
|
||||
.ok_or_else(|| anyhow::anyhow!("The contract code part type is undefined"))?;
|
||||
let block_key = era_compiler_llvm_context::EraVMFunctionBlockKey::new(code_type, destination);
|
||||
|
||||
let condition_pointer = context.evmla().stack[stack_height]
|
||||
.to_llvm()
|
||||
.into_pointer_value();
|
||||
let condition = context.build_load(
|
||||
era_compiler_llvm_context::EraVMPointer::new_stack_field(context, condition_pointer),
|
||||
format!("conditional_{block_key}_condition").as_str(),
|
||||
)?;
|
||||
let condition = context.builder().build_int_compare(
|
||||
inkwell::IntPredicate::NE,
|
||||
condition.into_int_value(),
|
||||
context.field_const(0),
|
||||
format!("conditional_{block_key}_condition_compared").as_str(),
|
||||
)?;
|
||||
|
||||
let then_block = context
|
||||
.current_function()
|
||||
.borrow()
|
||||
.evmla()
|
||||
.find_block(&block_key, &stack_hash)?;
|
||||
let join_block =
|
||||
context.append_basic_block(format!("conditional_{block_key}_join_block").as_str());
|
||||
|
||||
context.build_conditional_branch(condition, then_block.inner(), join_block)?;
|
||||
|
||||
context.set_basic_block(join_block);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,398 @@
|
||||
//!
|
||||
//! The EVM instruction.
|
||||
//!
|
||||
|
||||
pub mod codecopy;
|
||||
pub mod jump;
|
||||
pub mod name;
|
||||
pub mod stack;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use self::name::Name;
|
||||
|
||||
///
|
||||
/// The EVM instruction.
|
||||
///
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct Instruction {
|
||||
/// The opcode or tag identifier.
|
||||
pub name: Name,
|
||||
/// The optional value argument.
|
||||
pub value: Option<String>,
|
||||
|
||||
/// The source code identifier.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub source: Option<isize>,
|
||||
/// The source code location begin.
|
||||
pub begin: isize,
|
||||
/// The source code location end.
|
||||
pub end: isize,
|
||||
}
|
||||
|
||||
impl Instruction {
|
||||
///
|
||||
/// Returns the number of input stack arguments.
|
||||
///
|
||||
pub const fn input_size(&self, version: &semver::Version) -> usize {
|
||||
match self.name {
|
||||
Name::POP => 1,
|
||||
|
||||
Name::JUMP => 1,
|
||||
Name::JUMPI => 2,
|
||||
|
||||
Name::ADD => 2,
|
||||
Name::SUB => 2,
|
||||
Name::MUL => 2,
|
||||
Name::DIV => 2,
|
||||
Name::MOD => 2,
|
||||
Name::SDIV => 2,
|
||||
Name::SMOD => 2,
|
||||
|
||||
Name::LT => 2,
|
||||
Name::GT => 2,
|
||||
Name::EQ => 2,
|
||||
Name::ISZERO => 1,
|
||||
Name::SLT => 2,
|
||||
Name::SGT => 2,
|
||||
|
||||
Name::OR => 2,
|
||||
Name::XOR => 2,
|
||||
Name::NOT => 1,
|
||||
Name::AND => 2,
|
||||
Name::SHL => 2,
|
||||
Name::SHR => 2,
|
||||
Name::SAR => 2,
|
||||
Name::BYTE => 2,
|
||||
|
||||
Name::ADDMOD => 3,
|
||||
Name::MULMOD => 3,
|
||||
Name::EXP => 2,
|
||||
Name::SIGNEXTEND => 2,
|
||||
Name::SHA3 => 2,
|
||||
Name::KECCAK256 => 2,
|
||||
|
||||
Name::MLOAD => 1,
|
||||
Name::MSTORE => 2,
|
||||
Name::MSTORE8 => 2,
|
||||
Name::MCOPY => 3,
|
||||
|
||||
Name::SLOAD => 1,
|
||||
Name::SSTORE => 2,
|
||||
Name::TLOAD => 1,
|
||||
Name::TSTORE => 2,
|
||||
Name::PUSHIMMUTABLE => 0,
|
||||
Name::ASSIGNIMMUTABLE => {
|
||||
if version.minor >= 8 {
|
||||
2
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
Name::CALLDATALOAD => 1,
|
||||
Name::CALLDATACOPY => 3,
|
||||
Name::CODECOPY => 3,
|
||||
Name::RETURNDATACOPY => 3,
|
||||
Name::EXTCODESIZE => 1,
|
||||
Name::EXTCODEHASH => 1,
|
||||
|
||||
Name::CALL => 7,
|
||||
Name::CALLCODE => 7,
|
||||
Name::STATICCALL => 6,
|
||||
Name::DELEGATECALL => 6,
|
||||
|
||||
Name::RETURN => 2,
|
||||
Name::REVERT => 2,
|
||||
Name::SELFDESTRUCT => 1,
|
||||
|
||||
Name::LOG0 => 2,
|
||||
Name::LOG1 => 3,
|
||||
Name::LOG2 => 4,
|
||||
Name::LOG3 => 5,
|
||||
Name::LOG4 => 6,
|
||||
|
||||
Name::CREATE => 3,
|
||||
Name::CREATE2 => 4,
|
||||
|
||||
Name::ZK_CREATE => 3,
|
||||
Name::ZK_CREATE2 => 4,
|
||||
|
||||
Name::BALANCE => 1,
|
||||
|
||||
Name::BLOCKHASH => 1,
|
||||
Name::BLOBHASH => 1,
|
||||
|
||||
Name::EXTCODECOPY => 4,
|
||||
|
||||
Name::RecursiveCall { input_size, .. } => input_size,
|
||||
Name::RecursiveReturn { input_size } => input_size,
|
||||
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns the number of output stack arguments.
|
||||
///
|
||||
pub const fn output_size(&self) -> usize {
|
||||
match self.name {
|
||||
Name::PUSH => 1,
|
||||
Name::PUSH_Data => 1,
|
||||
Name::PUSH_Tag => 1,
|
||||
Name::PUSH_ContractHash => 1,
|
||||
Name::PUSH_ContractHashSize => 1,
|
||||
Name::PUSHLIB => 1,
|
||||
Name::PUSHDEPLOYADDRESS => 1,
|
||||
|
||||
Name::PUSH1 => 1,
|
||||
Name::PUSH2 => 1,
|
||||
Name::PUSH3 => 1,
|
||||
Name::PUSH4 => 1,
|
||||
Name::PUSH5 => 1,
|
||||
Name::PUSH6 => 1,
|
||||
Name::PUSH7 => 1,
|
||||
Name::PUSH8 => 1,
|
||||
Name::PUSH9 => 1,
|
||||
Name::PUSH10 => 1,
|
||||
Name::PUSH11 => 1,
|
||||
Name::PUSH12 => 1,
|
||||
Name::PUSH13 => 1,
|
||||
Name::PUSH14 => 1,
|
||||
Name::PUSH15 => 1,
|
||||
Name::PUSH16 => 1,
|
||||
Name::PUSH17 => 1,
|
||||
Name::PUSH18 => 1,
|
||||
Name::PUSH19 => 1,
|
||||
Name::PUSH20 => 1,
|
||||
Name::PUSH21 => 1,
|
||||
Name::PUSH22 => 1,
|
||||
Name::PUSH23 => 1,
|
||||
Name::PUSH24 => 1,
|
||||
Name::PUSH25 => 1,
|
||||
Name::PUSH26 => 1,
|
||||
Name::PUSH27 => 1,
|
||||
Name::PUSH28 => 1,
|
||||
Name::PUSH29 => 1,
|
||||
Name::PUSH30 => 1,
|
||||
Name::PUSH31 => 1,
|
||||
Name::PUSH32 => 1,
|
||||
|
||||
Name::DUP1 => 1,
|
||||
Name::DUP2 => 1,
|
||||
Name::DUP3 => 1,
|
||||
Name::DUP4 => 1,
|
||||
Name::DUP5 => 1,
|
||||
Name::DUP6 => 1,
|
||||
Name::DUP7 => 1,
|
||||
Name::DUP8 => 1,
|
||||
Name::DUP9 => 1,
|
||||
Name::DUP10 => 1,
|
||||
Name::DUP11 => 1,
|
||||
Name::DUP12 => 1,
|
||||
Name::DUP13 => 1,
|
||||
Name::DUP14 => 1,
|
||||
Name::DUP15 => 1,
|
||||
Name::DUP16 => 1,
|
||||
|
||||
Name::ADD => 1,
|
||||
Name::SUB => 1,
|
||||
Name::MUL => 1,
|
||||
Name::DIV => 1,
|
||||
Name::MOD => 1,
|
||||
Name::SDIV => 1,
|
||||
Name::SMOD => 1,
|
||||
|
||||
Name::LT => 1,
|
||||
Name::GT => 1,
|
||||
Name::EQ => 1,
|
||||
Name::ISZERO => 1,
|
||||
Name::SLT => 1,
|
||||
Name::SGT => 1,
|
||||
|
||||
Name::OR => 1,
|
||||
Name::XOR => 1,
|
||||
Name::NOT => 1,
|
||||
Name::AND => 1,
|
||||
Name::SHL => 1,
|
||||
Name::SHR => 1,
|
||||
Name::SAR => 1,
|
||||
Name::BYTE => 1,
|
||||
|
||||
Name::ADDMOD => 1,
|
||||
Name::MULMOD => 1,
|
||||
Name::EXP => 1,
|
||||
Name::SIGNEXTEND => 1,
|
||||
Name::SHA3 => 1,
|
||||
Name::KECCAK256 => 1,
|
||||
|
||||
Name::MLOAD => 1,
|
||||
|
||||
Name::SLOAD => 1,
|
||||
Name::TLOAD => 1,
|
||||
Name::PUSHIMMUTABLE => 1,
|
||||
|
||||
Name::CALLDATALOAD => 1,
|
||||
Name::CALLDATASIZE => 1,
|
||||
Name::CODESIZE => 1,
|
||||
Name::PUSHSIZE => 1,
|
||||
Name::RETURNDATASIZE => 1,
|
||||
Name::EXTCODESIZE => 1,
|
||||
Name::EXTCODEHASH => 1,
|
||||
|
||||
Name::CALL => 1,
|
||||
Name::CALLCODE => 1,
|
||||
Name::STATICCALL => 1,
|
||||
Name::DELEGATECALL => 1,
|
||||
|
||||
Name::CREATE => 1,
|
||||
Name::CREATE2 => 1,
|
||||
|
||||
Name::ZK_CREATE => 1,
|
||||
Name::ZK_CREATE2 => 1,
|
||||
|
||||
Name::ADDRESS => 1,
|
||||
Name::CALLER => 1,
|
||||
Name::TIMESTAMP => 1,
|
||||
Name::NUMBER => 1,
|
||||
|
||||
Name::CALLVALUE => 1,
|
||||
Name::GAS => 1,
|
||||
Name::BALANCE => 1,
|
||||
Name::SELFBALANCE => 1,
|
||||
|
||||
Name::GASLIMIT => 1,
|
||||
Name::GASPRICE => 1,
|
||||
Name::ORIGIN => 1,
|
||||
Name::CHAINID => 1,
|
||||
Name::BLOCKHASH => 1,
|
||||
Name::BLOBHASH => 1,
|
||||
Name::DIFFICULTY => 1,
|
||||
Name::PREVRANDAO => 1,
|
||||
Name::COINBASE => 1,
|
||||
Name::MSIZE => 1,
|
||||
|
||||
Name::BASEFEE => 1,
|
||||
Name::BLOBBASEFEE => 1,
|
||||
Name::PC => 1,
|
||||
|
||||
Name::RecursiveCall { output_size, .. } => output_size,
|
||||
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Replaces the instruction data aliases with the actual data.
|
||||
///
|
||||
pub fn replace_data_aliases(
|
||||
instructions: &mut [Self],
|
||||
mapping: &BTreeMap<String, String>,
|
||||
) -> anyhow::Result<()> {
|
||||
for instruction in instructions.iter_mut() {
|
||||
match instruction {
|
||||
Instruction {
|
||||
name: Name::PUSH_ContractHash | Name::PUSH_ContractHashSize,
|
||||
value: Some(value),
|
||||
..
|
||||
} => {
|
||||
*value = mapping.get(value.as_str()).cloned().ok_or_else(|| {
|
||||
anyhow::anyhow!("Contract alias `{}` data not found", value)
|
||||
})?;
|
||||
}
|
||||
Instruction {
|
||||
name: Name::PUSH_Data,
|
||||
value: Some(value),
|
||||
..
|
||||
} => {
|
||||
let mut key_extended =
|
||||
"0".repeat(era_compiler_common::BYTE_LENGTH_FIELD * 2 - value.len());
|
||||
key_extended.push_str(value.as_str());
|
||||
|
||||
*value = mapping.get(key_extended.as_str()).cloned().ok_or_else(|| {
|
||||
anyhow::anyhow!("Data chunk alias `{}` data not found", key_extended)
|
||||
})?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Initializes an `INVALID` instruction to terminate an invalid unreachable block part.
|
||||
///
|
||||
pub fn invalid(previous: &Self) -> Self {
|
||||
Self {
|
||||
name: Name::INVALID,
|
||||
value: None,
|
||||
|
||||
source: previous.source,
|
||||
begin: previous.begin,
|
||||
end: previous.end,
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Initializes a recursive function `Call` instruction.
|
||||
///
|
||||
pub fn recursive_call(
|
||||
name: String,
|
||||
entry_key: era_compiler_llvm_context::EraVMFunctionBlockKey,
|
||||
stack_hash: md5::Digest,
|
||||
input_size: usize,
|
||||
output_size: usize,
|
||||
return_address: era_compiler_llvm_context::EraVMFunctionBlockKey,
|
||||
previous: &Self,
|
||||
) -> Self {
|
||||
Self {
|
||||
name: Name::RecursiveCall {
|
||||
name,
|
||||
entry_key,
|
||||
stack_hash,
|
||||
input_size,
|
||||
output_size,
|
||||
return_address,
|
||||
},
|
||||
value: None,
|
||||
|
||||
source: previous.source,
|
||||
begin: previous.begin,
|
||||
end: previous.end,
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Initializes a recursive function `Return` instruction.
|
||||
///
|
||||
pub fn recursive_return(input_size: usize, previous: &Self) -> Self {
|
||||
Self {
|
||||
name: Name::RecursiveReturn { input_size },
|
||||
value: None,
|
||||
|
||||
source: previous.source,
|
||||
begin: previous.begin,
|
||||
end: previous.end,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Instruction {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let name = self.name.to_string();
|
||||
match self.name {
|
||||
Name::Tag => write!(f, "{:4}", name),
|
||||
_ => write!(f, "{:15}", name),
|
||||
}?;
|
||||
match self.value {
|
||||
Some(ref value) if value.len() <= 64 => write!(f, "{}", value)?,
|
||||
Some(ref value) => write!(f, "... {}", &value[value.len() - 60..])?,
|
||||
None => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,421 @@
|
||||
//!
|
||||
//! The EVM instruction name.
|
||||
//!
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
///
|
||||
/// The EVM instruction name.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub enum Name {
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH,
|
||||
/// Pushes a constant tag index.
|
||||
#[serde(rename = "PUSH [tag]")]
|
||||
PUSH_Tag,
|
||||
/// Pushes an unknown `data` value.
|
||||
#[serde(rename = "PUSH data")]
|
||||
PUSH_Data,
|
||||
/// Pushes a contract hash size.
|
||||
#[serde(rename = "PUSH #[$]")]
|
||||
PUSH_ContractHashSize,
|
||||
/// Pushes a contract hash.
|
||||
#[serde(rename = "PUSH [$]")]
|
||||
PUSH_ContractHash,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH1,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH2,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH3,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH4,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH5,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH6,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH7,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH8,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH9,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH10,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH11,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH12,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH13,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH14,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH15,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH16,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH17,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH18,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH19,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH20,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH21,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH22,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH23,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH24,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH25,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH26,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH27,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH28,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH29,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH30,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH31,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSH32,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
DUP1,
|
||||
/// The eponymous EVM instruction.
|
||||
DUP2,
|
||||
/// The eponymous EVM instruction.
|
||||
DUP3,
|
||||
/// The eponymous EVM instruction.
|
||||
DUP4,
|
||||
/// The eponymous EVM instruction.
|
||||
DUP5,
|
||||
/// The eponymous EVM instruction.
|
||||
DUP6,
|
||||
/// The eponymous EVM instruction.
|
||||
DUP7,
|
||||
/// The eponymous EVM instruction.
|
||||
DUP8,
|
||||
/// The eponymous EVM instruction.
|
||||
DUP9,
|
||||
/// The eponymous EVM instruction.
|
||||
DUP10,
|
||||
/// The eponymous EVM instruction.
|
||||
DUP11,
|
||||
/// The eponymous EVM instruction.
|
||||
DUP12,
|
||||
/// The eponymous EVM instruction.
|
||||
DUP13,
|
||||
/// The eponymous EVM instruction.
|
||||
DUP14,
|
||||
/// The eponymous EVM instruction.
|
||||
DUP15,
|
||||
/// The eponymous EVM instruction.
|
||||
DUP16,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
SWAP1,
|
||||
/// The eponymous EVM instruction.
|
||||
SWAP2,
|
||||
/// The eponymous EVM instruction.
|
||||
SWAP3,
|
||||
/// The eponymous EVM instruction.
|
||||
SWAP4,
|
||||
/// The eponymous EVM instruction.
|
||||
SWAP5,
|
||||
/// The eponymous EVM instruction.
|
||||
SWAP6,
|
||||
/// The eponymous EVM instruction.
|
||||
SWAP7,
|
||||
/// The eponymous EVM instruction.
|
||||
SWAP8,
|
||||
/// The eponymous EVM instruction.
|
||||
SWAP9,
|
||||
/// The eponymous EVM instruction.
|
||||
SWAP10,
|
||||
/// The eponymous EVM instruction.
|
||||
SWAP11,
|
||||
/// The eponymous EVM instruction.
|
||||
SWAP12,
|
||||
/// The eponymous EVM instruction.
|
||||
SWAP13,
|
||||
/// The eponymous EVM instruction.
|
||||
SWAP14,
|
||||
/// The eponymous EVM instruction.
|
||||
SWAP15,
|
||||
/// The eponymous EVM instruction.
|
||||
SWAP16,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
POP,
|
||||
|
||||
/// Sets the current basic code block.
|
||||
#[serde(rename = "tag")]
|
||||
Tag,
|
||||
/// The eponymous EVM instruction.
|
||||
JUMP,
|
||||
/// The eponymous EVM instruction.
|
||||
JUMPI,
|
||||
/// The eponymous EVM instruction.
|
||||
JUMPDEST,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
ADD,
|
||||
/// The eponymous EVM instruction.
|
||||
SUB,
|
||||
/// The eponymous EVM instruction.
|
||||
MUL,
|
||||
/// The eponymous EVM instruction.
|
||||
DIV,
|
||||
/// The eponymous EVM instruction.
|
||||
MOD,
|
||||
/// The eponymous EVM instruction.
|
||||
SDIV,
|
||||
/// The eponymous EVM instruction.
|
||||
SMOD,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
LT,
|
||||
/// The eponymous EVM instruction.
|
||||
GT,
|
||||
/// The eponymous EVM instruction.
|
||||
EQ,
|
||||
/// The eponymous EVM instruction.
|
||||
ISZERO,
|
||||
/// The eponymous EVM instruction.
|
||||
SLT,
|
||||
/// The eponymous EVM instruction.
|
||||
SGT,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
OR,
|
||||
/// The eponymous EVM instruction.
|
||||
XOR,
|
||||
/// The eponymous EVM instruction.
|
||||
NOT,
|
||||
/// The eponymous EVM instruction.
|
||||
AND,
|
||||
/// The eponymous EVM instruction.
|
||||
SHL,
|
||||
/// The eponymous EVM instruction.
|
||||
SHR,
|
||||
/// The eponymous EVM instruction.
|
||||
SAR,
|
||||
/// The eponymous EVM instruction.
|
||||
BYTE,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
ADDMOD,
|
||||
/// The eponymous EVM instruction.
|
||||
MULMOD,
|
||||
/// The eponymous EVM instruction.
|
||||
EXP,
|
||||
/// The eponymous EVM instruction.
|
||||
SIGNEXTEND,
|
||||
/// The eponymous EVM instruction.
|
||||
SHA3,
|
||||
/// The eponymous EVM instruction.
|
||||
KECCAK256,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
MLOAD,
|
||||
/// The eponymous EVM instruction.
|
||||
MSTORE,
|
||||
/// The eponymous EVM instruction.
|
||||
MSTORE8,
|
||||
/// The eponymous EVM instruction.
|
||||
MCOPY,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
SLOAD,
|
||||
/// The eponymous EVM instruction.
|
||||
SSTORE,
|
||||
/// The eponymous EVM instruction.
|
||||
TLOAD,
|
||||
/// The eponymous EVM instruction.
|
||||
TSTORE,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSHIMMUTABLE,
|
||||
/// The eponymous EVM instruction.
|
||||
ASSIGNIMMUTABLE,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
CALLDATALOAD,
|
||||
/// The eponymous EVM instruction.
|
||||
CALLDATASIZE,
|
||||
/// The eponymous EVM instruction.
|
||||
CALLDATACOPY,
|
||||
/// The eponymous EVM instruction.
|
||||
CODESIZE,
|
||||
/// The eponymous EVM instruction.
|
||||
CODECOPY,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSHSIZE,
|
||||
/// The eponymous EVM instruction.
|
||||
EXTCODESIZE,
|
||||
/// The eponymous EVM instruction.
|
||||
EXTCODEHASH,
|
||||
/// The eponymous EVM instruction.
|
||||
RETURNDATASIZE,
|
||||
/// The eponymous EVM instruction.
|
||||
RETURNDATACOPY,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
RETURN,
|
||||
/// The eponymous EVM instruction.
|
||||
REVERT,
|
||||
/// The eponymous EVM instruction.
|
||||
STOP,
|
||||
/// The eponymous EVM instruction.
|
||||
INVALID,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
LOG0,
|
||||
/// The eponymous EVM instruction.
|
||||
LOG1,
|
||||
/// The eponymous EVM instruction.
|
||||
LOG2,
|
||||
/// The eponymous EVM instruction.
|
||||
LOG3,
|
||||
/// The eponymous EVM instruction.
|
||||
LOG4,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
CALL,
|
||||
/// The eponymous EVM instruction.
|
||||
STATICCALL,
|
||||
/// The eponymous EVM instruction.
|
||||
DELEGATECALL,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
CREATE,
|
||||
/// The eponymous EVM instruction.
|
||||
CREATE2,
|
||||
|
||||
/// The eponymous EraVM instruction.
|
||||
#[serde(rename = "$ZK_CREATE")]
|
||||
ZK_CREATE,
|
||||
/// The eponymous EraVM instruction.
|
||||
#[serde(rename = "$ZK_CREATE2")]
|
||||
ZK_CREATE2,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
ADDRESS,
|
||||
/// The eponymous EVM instruction.
|
||||
CALLER,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
CALLVALUE,
|
||||
/// The eponymous EVM instruction.
|
||||
GAS,
|
||||
/// The eponymous EVM instruction.
|
||||
BALANCE,
|
||||
/// The eponymous EVM instruction.
|
||||
SELFBALANCE,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
PUSHLIB,
|
||||
/// The eponymous EVM instruction.
|
||||
PUSHDEPLOYADDRESS,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
GASLIMIT,
|
||||
/// The eponymous EVM instruction.
|
||||
GASPRICE,
|
||||
/// The eponymous EVM instruction.
|
||||
ORIGIN,
|
||||
/// The eponymous EVM instruction.
|
||||
CHAINID,
|
||||
/// The eponymous EVM instruction.
|
||||
TIMESTAMP,
|
||||
/// The eponymous EVM instruction.
|
||||
NUMBER,
|
||||
/// The eponymous EVM instruction.
|
||||
BLOCKHASH,
|
||||
/// The eponymous EVM instruction.
|
||||
BLOBHASH,
|
||||
/// The eponymous EVM instruction.
|
||||
DIFFICULTY,
|
||||
/// The eponymous EVM instruction.
|
||||
PREVRANDAO,
|
||||
/// The eponymous EVM instruction.
|
||||
COINBASE,
|
||||
/// The eponymous EVM instruction.
|
||||
BASEFEE,
|
||||
/// The eponymous EVM instruction.
|
||||
BLOBBASEFEE,
|
||||
/// The eponymous EVM instruction.
|
||||
MSIZE,
|
||||
|
||||
/// The eponymous EVM instruction.
|
||||
CALLCODE,
|
||||
/// The eponymous EVM instruction.
|
||||
PC,
|
||||
/// The eponymous EVM instruction.
|
||||
EXTCODECOPY,
|
||||
/// The eponymous EVM instruction.
|
||||
SELFDESTRUCT,
|
||||
|
||||
/// The recursive function call instruction.
|
||||
#[serde(skip)]
|
||||
RecursiveCall {
|
||||
/// The called function name.
|
||||
name: String,
|
||||
/// The called function key.
|
||||
entry_key: era_compiler_llvm_context::EraVMFunctionBlockKey,
|
||||
/// The stack state hash after return.
|
||||
stack_hash: md5::Digest,
|
||||
/// The input size.
|
||||
input_size: usize,
|
||||
/// The output size.
|
||||
output_size: usize,
|
||||
/// The return address.
|
||||
return_address: era_compiler_llvm_context::EraVMFunctionBlockKey,
|
||||
},
|
||||
/// The recursive function return instruction.
|
||||
#[serde(skip)]
|
||||
RecursiveReturn {
|
||||
/// The output size.
|
||||
input_size: usize,
|
||||
},
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Name {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Tag => write!(f, "Tag"),
|
||||
Self::RecursiveCall {
|
||||
name,
|
||||
entry_key,
|
||||
input_size,
|
||||
output_size,
|
||||
return_address,
|
||||
..
|
||||
} => write!(
|
||||
f,
|
||||
"RECURSIVE_CALL({}_{}, {}, {}, {})",
|
||||
name, entry_key, input_size, output_size, return_address
|
||||
),
|
||||
Self::RecursiveReturn { input_size } => write!(f, "RECURSIVE_RETURN({})", input_size),
|
||||
_ => write!(
|
||||
f,
|
||||
"{}",
|
||||
serde_json::to_string(self)
|
||||
.expect("Always valid")
|
||||
.trim_matches('\"')
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
//!
|
||||
//! Translates the stack memory operations.
|
||||
//!
|
||||
|
||||
use inkwell::values::BasicValue;
|
||||
|
||||
///
|
||||
/// Translates the ordinar value push.
|
||||
///
|
||||
pub fn push<'ctx, D>(
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<'ctx, D>,
|
||||
value: String,
|
||||
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
|
||||
where
|
||||
D: era_compiler_llvm_context::EraVMDependency + Clone,
|
||||
{
|
||||
let result = context
|
||||
.field_type()
|
||||
.const_int_from_string(
|
||||
value.to_ascii_uppercase().as_str(),
|
||||
inkwell::types::StringRadix::Hexadecimal,
|
||||
)
|
||||
.expect("Always valid")
|
||||
.as_basic_value_enum();
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
///
|
||||
/// Translates the block tag label push.
|
||||
///
|
||||
pub fn push_tag<'ctx, D>(
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<'ctx, D>,
|
||||
value: String,
|
||||
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
|
||||
where
|
||||
D: era_compiler_llvm_context::EraVMDependency + Clone,
|
||||
{
|
||||
let result = context
|
||||
.field_type()
|
||||
.const_int_from_string(value.as_str(), inkwell::types::StringRadix::Decimal)
|
||||
.expect("Always valid");
|
||||
Ok(result.as_basic_value_enum())
|
||||
}
|
||||
|
||||
///
|
||||
/// Translates the stack memory duplicate.
|
||||
///
|
||||
pub fn dup<'ctx, D>(
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<'ctx, D>,
|
||||
offset: usize,
|
||||
height: usize,
|
||||
original: &mut Option<String>,
|
||||
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
|
||||
where
|
||||
D: era_compiler_llvm_context::EraVMDependency + Clone,
|
||||
{
|
||||
let element = &context.evmla().stack[height - offset - 1];
|
||||
let value = context.build_load(
|
||||
era_compiler_llvm_context::EraVMPointer::new_stack_field(
|
||||
context,
|
||||
element.to_llvm().into_pointer_value(),
|
||||
),
|
||||
format!("dup{offset}").as_str(),
|
||||
)?;
|
||||
|
||||
*original = element.original.to_owned();
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
///
|
||||
/// Translates the stack memory swap.
|
||||
///
|
||||
pub fn swap<D>(
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<D>,
|
||||
offset: usize,
|
||||
height: usize,
|
||||
) -> anyhow::Result<()>
|
||||
where
|
||||
D: era_compiler_llvm_context::EraVMDependency + Clone,
|
||||
{
|
||||
let top_element = context.evmla().stack[height - 1].to_owned();
|
||||
let top_pointer = era_compiler_llvm_context::EraVMPointer::new_stack_field(
|
||||
context,
|
||||
top_element.to_llvm().into_pointer_value(),
|
||||
);
|
||||
let top_value = context.build_load(top_pointer, format!("swap{offset}_top_value").as_str())?;
|
||||
|
||||
let swap_element = context.evmla().stack[height - offset - 1].to_owned();
|
||||
let swap_pointer = era_compiler_llvm_context::EraVMPointer::new_stack_field(
|
||||
context,
|
||||
swap_element.to_llvm().into_pointer_value(),
|
||||
);
|
||||
let swap_value =
|
||||
context.build_load(swap_pointer, format!("swap{offset}_swap_value").as_str())?;
|
||||
|
||||
context.evmla_mut().stack[height - 1].original = swap_element.original.to_owned();
|
||||
context.evmla_mut().stack[height - offset - 1].original = top_element.original.to_owned();
|
||||
|
||||
context.build_store(top_pointer, swap_value)?;
|
||||
context.build_store(swap_pointer, top_value)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Translates the stack memory pop.
|
||||
///
|
||||
pub fn pop<D>(_context: &mut era_compiler_llvm_context::EraVMContext<D>) -> anyhow::Result<()>
|
||||
where
|
||||
D: era_compiler_llvm_context::EraVMDependency + Clone,
|
||||
{
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
//!
|
||||
//! The `solc --asm-json` output.
|
||||
//!
|
||||
|
||||
pub mod data;
|
||||
pub mod instruction;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::evmla::ethereal_ir::entry_link::EntryLink;
|
||||
use crate::evmla::ethereal_ir::EtherealIR;
|
||||
use crate::solc::standard_json::output::contract::evm::extra_metadata::ExtraMetadata;
|
||||
|
||||
use self::data::Data;
|
||||
use self::instruction::name::Name as InstructionName;
|
||||
use self::instruction::Instruction;
|
||||
|
||||
///
|
||||
/// The JSON assembly.
|
||||
///
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Assembly {
|
||||
/// The metadata string.
|
||||
#[serde(rename = ".auxdata")]
|
||||
pub auxdata: Option<String>,
|
||||
/// The deploy code instructions.
|
||||
#[serde(rename = ".code")]
|
||||
pub code: Option<Vec<Instruction>>,
|
||||
/// The runtime code.
|
||||
#[serde(rename = ".data")]
|
||||
pub data: Option<BTreeMap<String, Data>>,
|
||||
|
||||
/// The full contract path.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub full_path: Option<String>,
|
||||
/// The factory dependency paths.
|
||||
#[serde(default = "HashSet::new")]
|
||||
pub factory_dependencies: HashSet<String>,
|
||||
/// The EVMLA extra metadata.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub extra_metadata: Option<ExtraMetadata>,
|
||||
}
|
||||
|
||||
impl Assembly {
|
||||
///
|
||||
/// Gets the contract `keccak256` hash.
|
||||
///
|
||||
pub fn keccak256(&self) -> String {
|
||||
let json = serde_json::to_vec(self).expect("Always valid");
|
||||
era_compiler_llvm_context::eravm_utils::keccak256(json.as_slice())
|
||||
}
|
||||
|
||||
///
|
||||
/// Sets the full contract path.
|
||||
///
|
||||
pub fn set_full_path(&mut self, full_path: String) {
|
||||
self.full_path = Some(full_path);
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns the full contract path if it is set, or `<undefined>` otherwise.
|
||||
///
|
||||
/// # Panics
|
||||
/// If the `full_path` has not been set.
|
||||
///
|
||||
pub fn full_path(&self) -> &str {
|
||||
self.full_path
|
||||
.as_deref()
|
||||
.unwrap_or_else(|| panic!("The full path of some contracts is unset"))
|
||||
}
|
||||
|
||||
///
|
||||
/// Get the list of missing deployable libraries.
|
||||
///
|
||||
pub fn get_missing_libraries(&self) -> HashSet<String> {
|
||||
let mut missing_libraries = HashSet::new();
|
||||
if let Some(code) = self.code.as_ref() {
|
||||
for instruction in code.iter() {
|
||||
if let InstructionName::PUSHLIB = instruction.name {
|
||||
let library_path = instruction.value.to_owned().expect("Always exists");
|
||||
missing_libraries.insert(library_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(data) = self.data.as_ref() {
|
||||
for (_, data) in data.iter() {
|
||||
missing_libraries.extend(data.get_missing_libraries());
|
||||
}
|
||||
}
|
||||
missing_libraries
|
||||
}
|
||||
|
||||
///
|
||||
/// Replaces the deploy code dependencies with full contract path and returns the list.
|
||||
///
|
||||
pub fn deploy_dependencies_pass(
|
||||
&mut self,
|
||||
full_path: &str,
|
||||
hash_data_mapping: &BTreeMap<String, String>,
|
||||
) -> anyhow::Result<BTreeMap<String, String>> {
|
||||
let mut index_path_mapping = BTreeMap::new();
|
||||
let index = "0".repeat(era_compiler_common::BYTE_LENGTH_FIELD * 2);
|
||||
index_path_mapping.insert(index, full_path.to_owned());
|
||||
|
||||
let dependencies = match self.data.as_mut() {
|
||||
Some(dependencies) => dependencies,
|
||||
None => return Ok(index_path_mapping),
|
||||
};
|
||||
for (index, data) in dependencies.iter_mut() {
|
||||
if index == "0" {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut index_extended =
|
||||
"0".repeat(era_compiler_common::BYTE_LENGTH_FIELD * 2 - index.len());
|
||||
index_extended.push_str(index.as_str());
|
||||
|
||||
*data = match data {
|
||||
Data::Assembly(assembly) => {
|
||||
let hash = assembly.keccak256();
|
||||
let full_path =
|
||||
hash_data_mapping
|
||||
.get(hash.as_str())
|
||||
.cloned()
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!("Contract path not found for hash `{}`", hash)
|
||||
})?;
|
||||
self.factory_dependencies.insert(full_path.to_owned());
|
||||
|
||||
index_path_mapping.insert(index_extended, full_path.clone());
|
||||
Data::Path(full_path)
|
||||
}
|
||||
Data::Hash(hash) => {
|
||||
index_path_mapping.insert(index_extended, hash.to_owned());
|
||||
continue;
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
}
|
||||
|
||||
Ok(index_path_mapping)
|
||||
}
|
||||
|
||||
///
|
||||
/// Replaces the runtime code dependencies with full contract path and returns the list.
|
||||
///
|
||||
pub fn runtime_dependencies_pass(
|
||||
&mut self,
|
||||
full_path: &str,
|
||||
hash_data_mapping: &BTreeMap<String, String>,
|
||||
) -> anyhow::Result<BTreeMap<String, String>> {
|
||||
let mut index_path_mapping = BTreeMap::new();
|
||||
let index = "0".repeat(era_compiler_common::BYTE_LENGTH_FIELD * 2);
|
||||
index_path_mapping.insert(index, full_path.to_owned());
|
||||
|
||||
let dependencies = match self
|
||||
.data
|
||||
.as_mut()
|
||||
.and_then(|data| data.get_mut("0"))
|
||||
.and_then(|data| data.get_assembly_mut())
|
||||
.and_then(|assembly| assembly.data.as_mut())
|
||||
{
|
||||
Some(dependencies) => dependencies,
|
||||
None => return Ok(index_path_mapping),
|
||||
};
|
||||
for (index, data) in dependencies.iter_mut() {
|
||||
let mut index_extended =
|
||||
"0".repeat(era_compiler_common::BYTE_LENGTH_FIELD * 2 - index.len());
|
||||
index_extended.push_str(index.as_str());
|
||||
|
||||
*data = match data {
|
||||
Data::Assembly(assembly) => {
|
||||
let hash = assembly.keccak256();
|
||||
let full_path =
|
||||
hash_data_mapping
|
||||
.get(hash.as_str())
|
||||
.cloned()
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!("Contract path not found for hash `{}`", hash)
|
||||
})?;
|
||||
self.factory_dependencies.insert(full_path.to_owned());
|
||||
|
||||
index_path_mapping.insert(index_extended, full_path.clone());
|
||||
Data::Path(full_path)
|
||||
}
|
||||
Data::Hash(hash) => {
|
||||
index_path_mapping.insert(index_extended, hash.to_owned());
|
||||
continue;
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
}
|
||||
|
||||
Ok(index_path_mapping)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> era_compiler_llvm_context::EraVMWriteLLVM<D> for Assembly
|
||||
where
|
||||
D: era_compiler_llvm_context::EraVMDependency + Clone,
|
||||
{
|
||||
fn declare(
|
||||
&mut self,
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<D>,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut entry = era_compiler_llvm_context::EraVMEntryFunction::default();
|
||||
entry.declare(context)?;
|
||||
|
||||
let mut runtime = era_compiler_llvm_context::EraVMRuntime::new(
|
||||
era_compiler_llvm_context::EraVMAddressSpace::Heap,
|
||||
);
|
||||
runtime.declare(context)?;
|
||||
|
||||
era_compiler_llvm_context::EraVMDeployCodeFunction::new(
|
||||
era_compiler_llvm_context::EraVMDummyLLVMWritable::default(),
|
||||
)
|
||||
.declare(context)?;
|
||||
era_compiler_llvm_context::EraVMRuntimeCodeFunction::new(
|
||||
era_compiler_llvm_context::EraVMDummyLLVMWritable::default(),
|
||||
)
|
||||
.declare(context)?;
|
||||
|
||||
entry.into_llvm(context)?;
|
||||
|
||||
runtime.into_llvm(context)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn into_llvm(
|
||||
mut self,
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<D>,
|
||||
) -> anyhow::Result<()> {
|
||||
let full_path = self.full_path().to_owned();
|
||||
|
||||
if let Some(debug_config) = context.debug_config() {
|
||||
debug_config.dump_evmla(full_path.as_str(), self.to_string().as_str())?;
|
||||
}
|
||||
let deploy_code_blocks = EtherealIR::get_blocks(
|
||||
context.evmla().version.to_owned(),
|
||||
era_compiler_llvm_context::EraVMCodeType::Deploy,
|
||||
self.code
|
||||
.as_deref()
|
||||
.ok_or_else(|| anyhow::anyhow!("Deploy code instructions not found"))?,
|
||||
)?;
|
||||
|
||||
let data = self
|
||||
.data
|
||||
.ok_or_else(|| anyhow::anyhow!("Runtime code data not found"))?
|
||||
.remove("0")
|
||||
.expect("Always exists");
|
||||
if let Some(debug_config) = context.debug_config() {
|
||||
debug_config.dump_evmla(full_path.as_str(), data.to_string().as_str())?;
|
||||
}
|
||||
let runtime_code_instructions = match data {
|
||||
Data::Assembly(assembly) => assembly
|
||||
.code
|
||||
.ok_or_else(|| anyhow::anyhow!("Runtime code instructions not found"))?,
|
||||
Data::Hash(hash) => {
|
||||
anyhow::bail!("Expected runtime code instructions, found hash `{}`", hash)
|
||||
}
|
||||
Data::Path(path) => {
|
||||
anyhow::bail!("Expected runtime code instructions, found path `{}`", path)
|
||||
}
|
||||
};
|
||||
let runtime_code_blocks = EtherealIR::get_blocks(
|
||||
context.evmla().version.to_owned(),
|
||||
era_compiler_llvm_context::EraVMCodeType::Runtime,
|
||||
runtime_code_instructions.as_slice(),
|
||||
)?;
|
||||
|
||||
let extra_metadata = self.extra_metadata.take().unwrap_or_default();
|
||||
let mut blocks = deploy_code_blocks;
|
||||
blocks.extend(runtime_code_blocks);
|
||||
let mut ethereal_ir =
|
||||
EtherealIR::new(context.evmla().version.to_owned(), extra_metadata, blocks)?;
|
||||
if let Some(debug_config) = context.debug_config() {
|
||||
debug_config.dump_ethir(full_path.as_str(), ethereal_ir.to_string().as_str())?;
|
||||
}
|
||||
ethereal_ir.declare(context)?;
|
||||
ethereal_ir.into_llvm(context)?;
|
||||
|
||||
era_compiler_llvm_context::EraVMDeployCodeFunction::new(EntryLink::new(
|
||||
era_compiler_llvm_context::EraVMCodeType::Deploy,
|
||||
))
|
||||
.into_llvm(context)?;
|
||||
era_compiler_llvm_context::EraVMRuntimeCodeFunction::new(EntryLink::new(
|
||||
era_compiler_llvm_context::EraVMCodeType::Runtime,
|
||||
))
|
||||
.into_llvm(context)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Assembly {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if let Some(instructions) = self.code.as_ref() {
|
||||
for (index, instruction) in instructions.iter().enumerate() {
|
||||
match instruction.name {
|
||||
InstructionName::Tag => writeln!(f, "{index:03} {instruction}")?,
|
||||
_ => writeln!(f, "{index:03} {instruction}")?,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
//!
|
||||
//! The Ethereal IR entry function link.
|
||||
//!
|
||||
|
||||
use inkwell::values::BasicValue;
|
||||
|
||||
use crate::evmla::ethereal_ir::EtherealIR;
|
||||
|
||||
///
|
||||
/// The Ethereal IR entry function link.
|
||||
///
|
||||
/// The link represents branching between the deploy and runtime code.
|
||||
///
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EntryLink {
|
||||
/// The code part type.
|
||||
pub code_type: era_compiler_llvm_context::EraVMCodeType,
|
||||
}
|
||||
|
||||
impl EntryLink {
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new(code_type: era_compiler_llvm_context::EraVMCodeType) -> Self {
|
||||
Self { code_type }
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> era_compiler_llvm_context::EraVMWriteLLVM<D> for EntryLink
|
||||
where
|
||||
D: era_compiler_llvm_context::EraVMDependency + Clone,
|
||||
{
|
||||
fn into_llvm(
|
||||
self,
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<D>,
|
||||
) -> anyhow::Result<()> {
|
||||
let target = context
|
||||
.get_function(EtherealIR::DEFAULT_ENTRY_FUNCTION_NAME)
|
||||
.expect("Always exists")
|
||||
.borrow()
|
||||
.declaration();
|
||||
let is_deploy_code = match self.code_type {
|
||||
era_compiler_llvm_context::EraVMCodeType::Deploy => context
|
||||
.integer_type(era_compiler_common::BIT_LENGTH_BOOLEAN)
|
||||
.const_int(1, false),
|
||||
era_compiler_llvm_context::EraVMCodeType::Runtime => context
|
||||
.integer_type(era_compiler_common::BIT_LENGTH_BOOLEAN)
|
||||
.const_int(0, false),
|
||||
};
|
||||
context.build_invoke(
|
||||
target,
|
||||
&[is_deploy_code.as_basic_value_enum()],
|
||||
format!("call_link_{}", EtherealIR::DEFAULT_ENTRY_FUNCTION_NAME).as_str(),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,41 @@
|
||||
//!
|
||||
//! The Ethereal IR block element stack element.
|
||||
//!
|
||||
|
||||
///
|
||||
/// The Ethereal IR block element stack element.
|
||||
///
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Element {
|
||||
/// The runtime value.
|
||||
Value(String),
|
||||
/// The compile-time value.
|
||||
Constant(num::BigUint),
|
||||
/// The compile-time destination tag.
|
||||
Tag(num::BigUint),
|
||||
/// The compile-time path.
|
||||
Path(String),
|
||||
/// The compile-time hexadecimal data chunk.
|
||||
Data(String),
|
||||
/// The recursive function return address.
|
||||
ReturnAddress(usize),
|
||||
}
|
||||
|
||||
impl Element {
|
||||
pub fn value(identifier: String) -> Self {
|
||||
Self::Value(identifier)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Element {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Value(identifier) => write!(f, "V_{identifier}"),
|
||||
Self::Constant(value) => write!(f, "{value:X}"),
|
||||
Self::Tag(tag) => write!(f, "T_{tag}"),
|
||||
Self::Path(path) => write!(f, "{path}"),
|
||||
Self::Data(data) => write!(f, "{data}"),
|
||||
Self::ReturnAddress(_) => write!(f, "RETURN_ADDRESS"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
//!
|
||||
//! The Ethereal IR block element stack.
|
||||
//!
|
||||
|
||||
pub mod element;
|
||||
|
||||
use self::element::Element;
|
||||
|
||||
///
|
||||
/// The Ethereal IR block element stack.
|
||||
///
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Stack {
|
||||
/// The stack elements.
|
||||
pub elements: Vec<Element>,
|
||||
}
|
||||
|
||||
impl Stack {
|
||||
/// The default stack size.
|
||||
pub const DEFAULT_STACK_SIZE: usize = 16;
|
||||
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
elements: Vec::with_capacity(Self::DEFAULT_STACK_SIZE),
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
elements: Vec::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new_with_elements(elements: Vec<Element>) -> Self {
|
||||
Self { elements }
|
||||
}
|
||||
|
||||
///
|
||||
/// The stack state hash, which acts as a block identifier.
|
||||
///
|
||||
/// Each block clone has its own initial stack state, which uniquely identifies the block.
|
||||
///
|
||||
pub fn hash(&self) -> md5::Digest {
|
||||
let mut hash_context = md5::Context::new();
|
||||
for element in self.elements.iter() {
|
||||
match element {
|
||||
Element::Tag(tag) => hash_context.consume(tag.to_bytes_be()),
|
||||
_ => hash_context.consume([0]),
|
||||
}
|
||||
}
|
||||
hash_context.compute()
|
||||
}
|
||||
|
||||
///
|
||||
/// Pushes an element onto the stack.
|
||||
///
|
||||
pub fn push(&mut self, element: Element) {
|
||||
self.elements.push(element);
|
||||
}
|
||||
|
||||
///
|
||||
/// Appends another stack on top of this one.
|
||||
///
|
||||
pub fn append(&mut self, other: &mut Self) {
|
||||
self.elements.append(&mut other.elements);
|
||||
}
|
||||
|
||||
///
|
||||
/// Pops a stack element.
|
||||
///
|
||||
pub fn pop(&mut self) -> anyhow::Result<Element> {
|
||||
self.elements
|
||||
.pop()
|
||||
.ok_or_else(|| anyhow::anyhow!("Stack underflow"))
|
||||
}
|
||||
|
||||
///
|
||||
/// Pops the tag from the top.
|
||||
///
|
||||
pub fn pop_tag(&mut self) -> anyhow::Result<num::BigUint> {
|
||||
match self.elements.pop() {
|
||||
Some(Element::Tag(tag)) => Ok(tag),
|
||||
Some(element) => anyhow::bail!("Expected tag, found {}", element),
|
||||
None => anyhow::bail!("Stack underflow"),
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Swaps two stack elements.
|
||||
///
|
||||
pub fn swap(&mut self, index: usize) -> anyhow::Result<()> {
|
||||
if self.elements.len() < index + 1 {
|
||||
anyhow::bail!("Stack underflow");
|
||||
}
|
||||
|
||||
let length = self.elements.len();
|
||||
self.elements.swap(length - 1, length - 1 - index);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
/// Duplicates a stack element.
|
||||
///
|
||||
pub fn dup(&mut self, index: usize) -> anyhow::Result<Element> {
|
||||
if self.elements.len() < index {
|
||||
anyhow::bail!("Stack underflow");
|
||||
}
|
||||
|
||||
Ok(self.elements[self.elements.len() - index].to_owned())
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns the stack length.
|
||||
///
|
||||
pub fn len(&self) -> usize {
|
||||
self.elements.len()
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns an emptiness flag.
|
||||
///
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.elements.len() == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Stack {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"[ {} ]",
|
||||
self.elements
|
||||
.iter()
|
||||
.map(Element::to_string)
|
||||
.collect::<Vec<String>>()
|
||||
.join(" | ")
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
//!
|
||||
//! The Ethereal IR block.
|
||||
//!
|
||||
|
||||
pub mod element;
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use num::Zero;
|
||||
|
||||
use crate::evmla::assembly::instruction::name::Name as InstructionName;
|
||||
use crate::evmla::assembly::instruction::Instruction;
|
||||
|
||||
use self::element::stack::Stack as ElementStack;
|
||||
use self::element::Element;
|
||||
|
||||
///
|
||||
/// The Ethereal IR block.
|
||||
///
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Block {
|
||||
/// The Solidity compiler version.
|
||||
pub solc_version: semver::Version,
|
||||
/// The block key.
|
||||
pub key: era_compiler_llvm_context::EraVMFunctionBlockKey,
|
||||
/// The block instance.
|
||||
pub instance: Option<usize>,
|
||||
/// The block elements relevant to the stack consistency.
|
||||
pub elements: Vec<Element>,
|
||||
/// The block predecessors.
|
||||
pub predecessors: HashSet<(era_compiler_llvm_context::EraVMFunctionBlockKey, usize)>,
|
||||
/// The initial stack state.
|
||||
pub initial_stack: ElementStack,
|
||||
/// The stack.
|
||||
pub stack: ElementStack,
|
||||
/// The extra block hashes for alternative routes.
|
||||
pub extra_hashes: Vec<md5::Digest>,
|
||||
}
|
||||
|
||||
impl Block {
|
||||
/// The elements vector initial capacity.
|
||||
pub const ELEMENTS_VECTOR_DEFAULT_CAPACITY: usize = 64;
|
||||
/// The predecessors hashset initial capacity.
|
||||
pub const PREDECESSORS_HASHSET_DEFAULT_CAPACITY: usize = 4;
|
||||
|
||||
///
|
||||
/// Assembles a block from the sequence of instructions.
|
||||
///
|
||||
pub fn try_from_instructions(
|
||||
solc_version: semver::Version,
|
||||
code_type: era_compiler_llvm_context::EraVMCodeType,
|
||||
slice: &[Instruction],
|
||||
) -> anyhow::Result<(Self, usize)> {
|
||||
let mut cursor = 0;
|
||||
|
||||
let tag: num::BigUint = match slice[cursor].name {
|
||||
InstructionName::Tag => {
|
||||
let tag = slice[cursor]
|
||||
.value
|
||||
.as_deref()
|
||||
.expect("Always exists")
|
||||
.parse()
|
||||
.expect("Always valid");
|
||||
cursor += 1;
|
||||
tag
|
||||
}
|
||||
_ => num::BigUint::zero(),
|
||||
};
|
||||
|
||||
let mut block = Self {
|
||||
solc_version: solc_version.clone(),
|
||||
key: era_compiler_llvm_context::EraVMFunctionBlockKey::new(code_type, tag),
|
||||
instance: None,
|
||||
elements: Vec::with_capacity(Self::ELEMENTS_VECTOR_DEFAULT_CAPACITY),
|
||||
predecessors: HashSet::with_capacity(Self::PREDECESSORS_HASHSET_DEFAULT_CAPACITY),
|
||||
initial_stack: ElementStack::new(),
|
||||
stack: ElementStack::new(),
|
||||
extra_hashes: vec![],
|
||||
};
|
||||
|
||||
let mut dead_code = false;
|
||||
while cursor < slice.len() {
|
||||
if !dead_code {
|
||||
let element: Element = Element::new(solc_version.clone(), slice[cursor].to_owned());
|
||||
block.elements.push(element);
|
||||
}
|
||||
|
||||
match slice[cursor].name {
|
||||
InstructionName::RETURN
|
||||
| InstructionName::REVERT
|
||||
| InstructionName::STOP
|
||||
| InstructionName::INVALID => {
|
||||
cursor += 1;
|
||||
dead_code = true;
|
||||
}
|
||||
InstructionName::JUMP => {
|
||||
cursor += 1;
|
||||
dead_code = true;
|
||||
}
|
||||
InstructionName::Tag => {
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
cursor += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok((block, cursor))
|
||||
}
|
||||
|
||||
///
|
||||
/// Inserts a predecessor tag.
|
||||
///
|
||||
pub fn insert_predecessor(
|
||||
&mut self,
|
||||
key: era_compiler_llvm_context::EraVMFunctionBlockKey,
|
||||
instance: usize,
|
||||
) {
|
||||
self.predecessors.insert((key, instance));
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> era_compiler_llvm_context::EraVMWriteLLVM<D> for Block
|
||||
where
|
||||
D: era_compiler_llvm_context::EraVMDependency + Clone,
|
||||
{
|
||||
fn into_llvm(
|
||||
self,
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<D>,
|
||||
) -> anyhow::Result<()> {
|
||||
context.set_code_type(self.key.code_type);
|
||||
|
||||
for element in self.elements.into_iter() {
|
||||
element.into_llvm(context)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Block {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(
|
||||
f,
|
||||
"block_{}/{}: {}",
|
||||
self.key,
|
||||
self.instance.unwrap_or_default(),
|
||||
if self.predecessors.is_empty() {
|
||||
"".to_owned()
|
||||
} else {
|
||||
format!(
|
||||
"(predecessors: {})",
|
||||
self.predecessors
|
||||
.iter()
|
||||
.map(|(key, instance)| format!("{}/{}", key, instance))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
)
|
||||
},
|
||||
)?;
|
||||
for element in self.elements.iter() {
|
||||
writeln!(f, " {element}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,35 @@
|
||||
//!
|
||||
//! The Ethereal IR block queue element.
|
||||
//!
|
||||
|
||||
use crate::evmla::ethereal_ir::function::block::element::stack::Stack;
|
||||
|
||||
///
|
||||
/// The Ethereal IR block queue element.
|
||||
///
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct QueueElement {
|
||||
/// The block key.
|
||||
pub block_key: era_compiler_llvm_context::EraVMFunctionBlockKey,
|
||||
/// The block predecessor.
|
||||
pub predecessor: Option<(era_compiler_llvm_context::EraVMFunctionBlockKey, usize)>,
|
||||
/// The predecessor's last stack state.
|
||||
pub stack: Stack,
|
||||
}
|
||||
|
||||
impl QueueElement {
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new(
|
||||
block_key: era_compiler_llvm_context::EraVMFunctionBlockKey,
|
||||
predecessor: Option<(era_compiler_llvm_context::EraVMFunctionBlockKey, usize)>,
|
||||
stack: Stack,
|
||||
) -> Self {
|
||||
Self {
|
||||
block_key,
|
||||
predecessor,
|
||||
stack,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
//!
|
||||
//! The Ethereal IR function type.
|
||||
//!
|
||||
|
||||
///
|
||||
/// The Ethereal IR function type.
|
||||
///
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Type {
|
||||
/// The initial function, combining deploy and runtime code.
|
||||
Initial,
|
||||
/// The recursive function with a specific block starting its recursive context.
|
||||
Recursive {
|
||||
/// The function name.
|
||||
name: String,
|
||||
/// The function initial block key.
|
||||
block_key: era_compiler_llvm_context::EraVMFunctionBlockKey,
|
||||
/// The size of stack input (in cells or 256-bit words).
|
||||
input_size: usize,
|
||||
/// The size of stack output (in cells or 256-bit words).
|
||||
output_size: usize,
|
||||
},
|
||||
}
|
||||
|
||||
impl Type {
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new_initial() -> Self {
|
||||
Self::Initial
|
||||
}
|
||||
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new_recursive(
|
||||
name: String,
|
||||
block_key: era_compiler_llvm_context::EraVMFunctionBlockKey,
|
||||
input_size: usize,
|
||||
output_size: usize,
|
||||
) -> Self {
|
||||
Self::Recursive {
|
||||
name,
|
||||
block_key,
|
||||
input_size,
|
||||
output_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
//!
|
||||
//! The Ethereal IR block visited element.
|
||||
//!
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
///
|
||||
/// The Ethereal IR block visited element.
|
||||
///
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct VisitedElement {
|
||||
/// The block key.
|
||||
pub block_key: era_compiler_llvm_context::EraVMFunctionBlockKey,
|
||||
/// The initial stack state hash.
|
||||
pub stack_hash: md5::Digest,
|
||||
}
|
||||
|
||||
impl VisitedElement {
|
||||
///
|
||||
/// A shortcut constructor.
|
||||
///
|
||||
pub fn new(
|
||||
block_key: era_compiler_llvm_context::EraVMFunctionBlockKey,
|
||||
stack_hash: md5::Digest,
|
||||
) -> Self {
|
||||
Self {
|
||||
block_key,
|
||||
stack_hash,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for VisitedElement {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for VisitedElement {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
match (self.block_key.code_type, other.block_key.code_type) {
|
||||
(
|
||||
era_compiler_llvm_context::EraVMCodeType::Deploy,
|
||||
era_compiler_llvm_context::EraVMCodeType::Runtime,
|
||||
) => Ordering::Less,
|
||||
(
|
||||
era_compiler_llvm_context::EraVMCodeType::Runtime,
|
||||
era_compiler_llvm_context::EraVMCodeType::Deploy,
|
||||
) => Ordering::Greater,
|
||||
(
|
||||
era_compiler_llvm_context::EraVMCodeType::Deploy,
|
||||
era_compiler_llvm_context::EraVMCodeType::Deploy,
|
||||
)
|
||||
| (
|
||||
era_compiler_llvm_context::EraVMCodeType::Runtime,
|
||||
era_compiler_llvm_context::EraVMCodeType::Runtime,
|
||||
) => {
|
||||
let tag_comparison = self.block_key.tag.cmp(&other.block_key.tag);
|
||||
if tag_comparison == Ordering::Equal {
|
||||
if self.stack_hash == other.stack_hash {
|
||||
Ordering::Equal
|
||||
} else {
|
||||
Ordering::Less
|
||||
}
|
||||
} else {
|
||||
tag_comparison
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
//!
|
||||
//! The Ethereal IR of the EVM bytecode.
|
||||
//!
|
||||
|
||||
pub mod entry_link;
|
||||
pub mod function;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::evmla::assembly::instruction::Instruction;
|
||||
use crate::solc::standard_json::output::contract::evm::extra_metadata::ExtraMetadata;
|
||||
|
||||
use self::function::block::Block;
|
||||
use self::function::r#type::Type as FunctionType;
|
||||
use self::function::Function;
|
||||
|
||||
///
|
||||
/// The Ethereal IR of the EVM bytecode.
|
||||
///
|
||||
/// The Ethereal IR (EthIR) is a special IR between the EVM legacy assembly and LLVM IR. It is
|
||||
/// created to facilitate the translation and provide an additional environment for applying some
|
||||
/// transformations, duplicating parts of the call and control flow graphs, tracking the
|
||||
/// data flow, and a few more algorithms of static analysis.
|
||||
///
|
||||
/// The most important feature of EthIR is flattening the block tags and duplicating blocks for
|
||||
/// each of initial states of the stack. The LLVM IR supports only static control flow, so the
|
||||
/// stack state must be known all the way throughout the program.
|
||||
///
|
||||
#[derive(Debug)]
|
||||
pub struct EtherealIR {
|
||||
/// The Solidity compiler version.
|
||||
pub solc_version: semver::Version,
|
||||
/// The EVMLA extra metadata.
|
||||
pub extra_metadata: ExtraMetadata,
|
||||
/// The all-inlined function.
|
||||
pub entry_function: Function,
|
||||
/// The recursive functions.
|
||||
pub recursive_functions: BTreeMap<era_compiler_llvm_context::EraVMFunctionBlockKey, Function>,
|
||||
}
|
||||
|
||||
impl EtherealIR {
|
||||
/// The default entry function name.
|
||||
pub const DEFAULT_ENTRY_FUNCTION_NAME: &'static str = "main";
|
||||
|
||||
/// The blocks hashmap initial capacity.
|
||||
pub const BLOCKS_HASHMAP_DEFAULT_CAPACITY: usize = 64;
|
||||
|
||||
///
|
||||
/// Assembles a sequence of functions from the sequence of instructions.
|
||||
///
|
||||
pub fn new(
|
||||
solc_version: semver::Version,
|
||||
extra_metadata: ExtraMetadata,
|
||||
blocks: HashMap<era_compiler_llvm_context::EraVMFunctionBlockKey, Block>,
|
||||
) -> anyhow::Result<Self> {
|
||||
let mut entry_function = Function::new(solc_version.clone(), FunctionType::new_initial());
|
||||
let mut recursive_functions = BTreeMap::new();
|
||||
let mut visited_functions = BTreeSet::new();
|
||||
entry_function.traverse(
|
||||
&blocks,
|
||||
&mut recursive_functions,
|
||||
&extra_metadata,
|
||||
&mut visited_functions,
|
||||
)?;
|
||||
|
||||
Ok(Self {
|
||||
solc_version,
|
||||
extra_metadata,
|
||||
entry_function,
|
||||
recursive_functions,
|
||||
})
|
||||
}
|
||||
|
||||
///
|
||||
/// Gets blocks for the specified type of the contract code.
|
||||
///
|
||||
pub fn get_blocks(
|
||||
solc_version: semver::Version,
|
||||
code_type: era_compiler_llvm_context::EraVMCodeType,
|
||||
instructions: &[Instruction],
|
||||
) -> anyhow::Result<HashMap<era_compiler_llvm_context::EraVMFunctionBlockKey, Block>> {
|
||||
let mut blocks = HashMap::with_capacity(Self::BLOCKS_HASHMAP_DEFAULT_CAPACITY);
|
||||
let mut offset = 0;
|
||||
|
||||
while offset < instructions.len() {
|
||||
let (block, size) = Block::try_from_instructions(
|
||||
solc_version.clone(),
|
||||
code_type,
|
||||
&instructions[offset..],
|
||||
)?;
|
||||
blocks.insert(
|
||||
era_compiler_llvm_context::EraVMFunctionBlockKey::new(
|
||||
code_type,
|
||||
block.key.tag.clone(),
|
||||
),
|
||||
block,
|
||||
);
|
||||
offset += size;
|
||||
}
|
||||
|
||||
Ok(blocks)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> era_compiler_llvm_context::EraVMWriteLLVM<D> for EtherealIR
|
||||
where
|
||||
D: era_compiler_llvm_context::EraVMDependency + Clone,
|
||||
{
|
||||
fn declare(
|
||||
&mut self,
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<D>,
|
||||
) -> anyhow::Result<()> {
|
||||
self.entry_function.declare(context)?;
|
||||
|
||||
for (_key, function) in self.recursive_functions.iter_mut() {
|
||||
function.declare(context)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn into_llvm(
|
||||
self,
|
||||
context: &mut era_compiler_llvm_context::EraVMContext<D>,
|
||||
) -> anyhow::Result<()> {
|
||||
context.evmla_mut().stack = vec![];
|
||||
|
||||
self.entry_function.into_llvm(context)?;
|
||||
|
||||
for (_key, function) in self.recursive_functions.into_iter() {
|
||||
function.into_llvm(context)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for EtherealIR {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "{}", self.entry_function)?;
|
||||
|
||||
for (_key, function) in self.recursive_functions.iter() {
|
||||
writeln!(f, "{}", function)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
//!
|
||||
//! The EVM legacy assembly compiling tools.
|
||||
//!
|
||||
|
||||
pub mod assembly;
|
||||
pub mod ethereal_ir;
|
||||
Reference in New Issue
Block a user