mirror of
https://github.com/pezkuwichain/revive.git
synced 2026-04-22 11:27:59 +00:00
1330 lines
51 KiB
Rust
1330 lines
51 KiB
Rust
//! The Ethereal IR function.
|
|
|
|
pub mod block;
|
|
pub mod queue_element;
|
|
pub mod r#type;
|
|
pub mod visited_element;
|
|
|
|
use std::collections::BTreeMap;
|
|
use std::collections::BTreeSet;
|
|
use std::collections::HashMap;
|
|
|
|
use inkwell::types::BasicType;
|
|
use inkwell::values::BasicValue;
|
|
use num::CheckedAdd;
|
|
use num::CheckedDiv;
|
|
use num::CheckedMul;
|
|
use num::CheckedSub;
|
|
use num::Num;
|
|
use num::One;
|
|
use num::ToPrimitive;
|
|
use num::Zero;
|
|
|
|
use crate::compiler::standard_json::output::contract::evm::extra_metadata::recursive_function::RecursiveFunction;
|
|
use crate::compiler::standard_json::output::contract::evm::extra_metadata::ExtraMetadata;
|
|
use crate::evmla::assembly::instruction::name::Name as InstructionName;
|
|
use crate::evmla::assembly::instruction::Instruction;
|
|
use crate::evmla::ethereal_ir::function::block::element::stack::element::Element;
|
|
use crate::evmla::ethereal_ir::function::block::element::stack::Stack;
|
|
use crate::evmla::ethereal_ir::EtherealIR;
|
|
|
|
use self::block::element::stack::element::Element as StackElement;
|
|
use self::block::element::Element as BlockElement;
|
|
use self::block::Block;
|
|
use self::queue_element::QueueElement;
|
|
use self::r#type::Type;
|
|
use self::visited_element::VisitedElement;
|
|
|
|
/// The Ethereal IR function.
|
|
#[derive(Debug, Clone)]
|
|
pub struct Function {
|
|
/// The Solidity compiler version.
|
|
pub solc_version: semver::Version,
|
|
/// The function name.
|
|
pub name: String,
|
|
/// The separately labelled blocks.
|
|
pub blocks: BTreeMap<revive_llvm_context::PolkaVMFunctionBlockKey, Vec<Block>>,
|
|
/// The function type.
|
|
pub r#type: Type,
|
|
/// The function stack size.
|
|
pub stack_size: usize,
|
|
}
|
|
|
|
impl Function {
|
|
/// A shortcut constructor.
|
|
pub fn new(solc_version: semver::Version, r#type: Type) -> Self {
|
|
let name = match r#type {
|
|
Type::Initial => EtherealIR::DEFAULT_ENTRY_FUNCTION_NAME.to_string(),
|
|
Type::Recursive {
|
|
ref name,
|
|
ref block_key,
|
|
..
|
|
} => format!("{name}_{block_key}"),
|
|
};
|
|
|
|
Self {
|
|
solc_version,
|
|
name,
|
|
blocks: BTreeMap::new(),
|
|
r#type,
|
|
stack_size: 0,
|
|
}
|
|
}
|
|
|
|
/// Runs the function block traversal.
|
|
pub fn traverse(
|
|
&mut self,
|
|
blocks: &HashMap<revive_llvm_context::PolkaVMFunctionBlockKey, Block>,
|
|
functions: &mut BTreeMap<revive_llvm_context::PolkaVMFunctionBlockKey, Self>,
|
|
extra_metadata: &ExtraMetadata,
|
|
visited_functions: &mut BTreeSet<VisitedElement>,
|
|
) -> anyhow::Result<()> {
|
|
let mut visited_blocks = BTreeSet::new();
|
|
|
|
match self.r#type {
|
|
Type::Initial => {
|
|
for code_type in [
|
|
revive_llvm_context::PolkaVMCodeType::Deploy,
|
|
revive_llvm_context::PolkaVMCodeType::Runtime,
|
|
] {
|
|
self.consume_block(
|
|
blocks,
|
|
functions,
|
|
extra_metadata,
|
|
visited_functions,
|
|
&mut visited_blocks,
|
|
QueueElement::new(
|
|
revive_llvm_context::PolkaVMFunctionBlockKey::new(
|
|
code_type,
|
|
num::BigUint::zero(),
|
|
),
|
|
None,
|
|
Stack::new(),
|
|
),
|
|
)?;
|
|
}
|
|
}
|
|
Type::Recursive {
|
|
ref block_key,
|
|
input_size,
|
|
output_size,
|
|
..
|
|
} => {
|
|
let mut stack = Stack::with_capacity(1 + input_size);
|
|
stack.push(Element::ReturnAddress(output_size));
|
|
stack.append(&mut Stack::new_with_elements(vec![
|
|
Element::value(
|
|
"ARGUMENT".to_owned()
|
|
);
|
|
input_size
|
|
]));
|
|
|
|
self.consume_block(
|
|
blocks,
|
|
functions,
|
|
extra_metadata,
|
|
visited_functions,
|
|
&mut visited_blocks,
|
|
QueueElement::new(block_key.to_owned(), None, stack),
|
|
)?;
|
|
}
|
|
}
|
|
|
|
self.finalize();
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Consumes the entry or a conditional block attached to another one.
|
|
fn consume_block(
|
|
&mut self,
|
|
blocks: &HashMap<revive_llvm_context::PolkaVMFunctionBlockKey, Block>,
|
|
functions: &mut BTreeMap<revive_llvm_context::PolkaVMFunctionBlockKey, Self>,
|
|
extra_metadata: &ExtraMetadata,
|
|
visited_functions: &mut BTreeSet<VisitedElement>,
|
|
visited_blocks: &mut BTreeSet<VisitedElement>,
|
|
mut queue_element: QueueElement,
|
|
) -> anyhow::Result<()> {
|
|
let version = self.solc_version.to_owned();
|
|
|
|
let mut queue = vec![];
|
|
|
|
let mut block = blocks
|
|
.get(&queue_element.block_key)
|
|
.cloned()
|
|
.ok_or_else(|| {
|
|
anyhow::anyhow!("Undeclared destination block {}", queue_element.block_key)
|
|
})?;
|
|
block.initial_stack = queue_element.stack.clone();
|
|
let block = self.insert_block(block);
|
|
block.stack = block.initial_stack.clone();
|
|
if let Some(predecessor) = queue_element.predecessor.take() {
|
|
block.insert_predecessor(predecessor.0, predecessor.1);
|
|
}
|
|
|
|
let visited_element =
|
|
VisitedElement::new(queue_element.block_key.clone(), queue_element.stack.hash());
|
|
if visited_blocks.contains(&visited_element) {
|
|
return Ok(());
|
|
}
|
|
visited_blocks.insert(visited_element);
|
|
|
|
let mut block_size = 0;
|
|
for block_element in block.elements.iter_mut() {
|
|
block_size += 1;
|
|
|
|
if Self::handle_instruction(
|
|
blocks,
|
|
functions,
|
|
extra_metadata,
|
|
visited_functions,
|
|
block.key.code_type,
|
|
block.instance.unwrap_or_default(),
|
|
&mut block.stack,
|
|
block_element,
|
|
&version,
|
|
&mut queue,
|
|
&mut queue_element,
|
|
)
|
|
.is_err()
|
|
{
|
|
block_element.instruction = Instruction::invalid(&block_element.instruction);
|
|
block_element.stack = block.stack.clone();
|
|
break;
|
|
}
|
|
}
|
|
block.elements.truncate(block_size);
|
|
|
|
for element in queue.into_iter() {
|
|
self.consume_block(
|
|
blocks,
|
|
functions,
|
|
extra_metadata,
|
|
visited_functions,
|
|
visited_blocks,
|
|
element,
|
|
)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Processes an instruction, returning an error, if there is an invalid stack state.
|
|
/// The blocks with an invalid stack state are considered being partially unreachable, and
|
|
/// the invalid part is truncated after terminating with an `INVALID` instruction.
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn handle_instruction(
|
|
blocks: &HashMap<revive_llvm_context::PolkaVMFunctionBlockKey, Block>,
|
|
functions: &mut BTreeMap<revive_llvm_context::PolkaVMFunctionBlockKey, Self>,
|
|
extra_metadata: &ExtraMetadata,
|
|
visited_functions: &mut BTreeSet<VisitedElement>,
|
|
code_type: revive_llvm_context::PolkaVMCodeType,
|
|
instance: usize,
|
|
block_stack: &mut Stack,
|
|
block_element: &mut BlockElement,
|
|
version: &semver::Version,
|
|
queue: &mut Vec<QueueElement>,
|
|
queue_element: &mut QueueElement,
|
|
) -> anyhow::Result<()> {
|
|
let (stack_output, queue_element) = match block_element.instruction {
|
|
Instruction {
|
|
name: InstructionName::PUSH_Tag,
|
|
value: Some(ref tag),
|
|
..
|
|
} => {
|
|
let tag: num::BigUint = tag.parse().expect("Always valid");
|
|
(vec![Element::Tag(tag & num::BigUint::from(u64::MAX))], None)
|
|
}
|
|
ref instruction @ Instruction {
|
|
name: InstructionName::JUMP,
|
|
..
|
|
} => {
|
|
queue_element.predecessor = Some((queue_element.block_key.clone(), instance));
|
|
|
|
let block_key = match block_stack
|
|
.elements
|
|
.last()
|
|
.ok_or_else(|| anyhow::anyhow!("Destination tag is missing"))?
|
|
{
|
|
Element::Tag(destination) if destination > &num::BigUint::from(u32::MAX) => {
|
|
revive_llvm_context::PolkaVMFunctionBlockKey::new(
|
|
revive_llvm_context::PolkaVMCodeType::Runtime,
|
|
destination.to_owned() - num::BigUint::from(1u64 << 32),
|
|
)
|
|
}
|
|
Element::Tag(destination) => revive_llvm_context::PolkaVMFunctionBlockKey::new(
|
|
code_type,
|
|
destination.to_owned(),
|
|
),
|
|
Element::ReturnAddress(output_size) => {
|
|
block_element.instruction =
|
|
Instruction::recursive_return(1 + output_size, instruction);
|
|
Self::update_io_data(block_stack, block_element, 1 + output_size, vec![])?;
|
|
return Ok(());
|
|
}
|
|
element => {
|
|
return Err(anyhow::anyhow!(
|
|
"The {} instruction expected a tag or return address, found {}",
|
|
instruction.name,
|
|
element
|
|
));
|
|
}
|
|
};
|
|
|
|
let (next_block_key, stack_output) =
|
|
if let Some(recursive_function) = extra_metadata.get(&block_key) {
|
|
Self::handle_recursive_function_call(
|
|
recursive_function,
|
|
blocks,
|
|
functions,
|
|
extra_metadata,
|
|
visited_functions,
|
|
block_key,
|
|
block_stack,
|
|
block_element,
|
|
version,
|
|
)?
|
|
} else {
|
|
(block_key, vec![])
|
|
};
|
|
|
|
(
|
|
stack_output,
|
|
Some(QueueElement::new(
|
|
next_block_key,
|
|
queue_element.predecessor.clone(),
|
|
Stack::new(),
|
|
)),
|
|
)
|
|
}
|
|
ref instruction @ Instruction {
|
|
name: InstructionName::JUMPI,
|
|
..
|
|
} => {
|
|
queue_element.predecessor = Some((queue_element.block_key.clone(), instance));
|
|
|
|
let block_key = match block_stack
|
|
.elements
|
|
.last()
|
|
.ok_or_else(|| anyhow::anyhow!("Destination tag is missing"))?
|
|
{
|
|
Element::Tag(destination) if destination > &num::BigUint::from(u32::MAX) => {
|
|
revive_llvm_context::PolkaVMFunctionBlockKey::new(
|
|
revive_llvm_context::PolkaVMCodeType::Runtime,
|
|
destination.to_owned() - num::BigUint::from(1u64 << 32),
|
|
)
|
|
}
|
|
Element::Tag(destination) => revive_llvm_context::PolkaVMFunctionBlockKey::new(
|
|
code_type,
|
|
destination.to_owned(),
|
|
),
|
|
element => {
|
|
return Err(anyhow::anyhow!(
|
|
"The {} instruction expected a tag or return address, found {}",
|
|
instruction.name,
|
|
element
|
|
));
|
|
}
|
|
};
|
|
|
|
(
|
|
vec![],
|
|
Some(QueueElement::new(
|
|
block_key,
|
|
queue_element.predecessor.clone(),
|
|
Stack::new(),
|
|
)),
|
|
)
|
|
}
|
|
Instruction {
|
|
name: InstructionName::Tag,
|
|
value: Some(ref tag),
|
|
..
|
|
} => {
|
|
let tag: num::BigUint = tag.parse().expect("Always valid");
|
|
let block_key = revive_llvm_context::PolkaVMFunctionBlockKey::new(code_type, tag);
|
|
|
|
queue_element.predecessor = Some((queue_element.block_key.clone(), instance));
|
|
queue_element.block_key = block_key.clone();
|
|
|
|
(
|
|
vec![],
|
|
Some(QueueElement::new(
|
|
block_key,
|
|
queue_element.predecessor.clone(),
|
|
Stack::new(),
|
|
)),
|
|
)
|
|
}
|
|
|
|
Instruction {
|
|
name: InstructionName::SWAP1,
|
|
..
|
|
} => {
|
|
block_stack.swap(1)?;
|
|
(vec![], None)
|
|
}
|
|
Instruction {
|
|
name: InstructionName::SWAP2,
|
|
..
|
|
} => {
|
|
block_stack.swap(2)?;
|
|
(vec![], None)
|
|
}
|
|
Instruction {
|
|
name: InstructionName::SWAP3,
|
|
..
|
|
} => {
|
|
block_stack.swap(3)?;
|
|
(vec![], None)
|
|
}
|
|
Instruction {
|
|
name: InstructionName::SWAP4,
|
|
..
|
|
} => {
|
|
block_stack.swap(4)?;
|
|
(vec![], None)
|
|
}
|
|
Instruction {
|
|
name: InstructionName::SWAP5,
|
|
..
|
|
} => {
|
|
block_stack.swap(5)?;
|
|
(vec![], None)
|
|
}
|
|
Instruction {
|
|
name: InstructionName::SWAP6,
|
|
..
|
|
} => {
|
|
block_stack.swap(6)?;
|
|
(vec![], None)
|
|
}
|
|
Instruction {
|
|
name: InstructionName::SWAP7,
|
|
..
|
|
} => {
|
|
block_stack.swap(7)?;
|
|
(vec![], None)
|
|
}
|
|
Instruction {
|
|
name: InstructionName::SWAP8,
|
|
..
|
|
} => {
|
|
block_stack.swap(8)?;
|
|
(vec![], None)
|
|
}
|
|
Instruction {
|
|
name: InstructionName::SWAP9,
|
|
..
|
|
} => {
|
|
block_stack.swap(9)?;
|
|
(vec![], None)
|
|
}
|
|
Instruction {
|
|
name: InstructionName::SWAP10,
|
|
..
|
|
} => {
|
|
block_stack.swap(10)?;
|
|
(vec![], None)
|
|
}
|
|
Instruction {
|
|
name: InstructionName::SWAP11,
|
|
..
|
|
} => {
|
|
block_stack.swap(11)?;
|
|
(vec![], None)
|
|
}
|
|
Instruction {
|
|
name: InstructionName::SWAP12,
|
|
..
|
|
} => {
|
|
block_stack.swap(12)?;
|
|
(vec![], None)
|
|
}
|
|
Instruction {
|
|
name: InstructionName::SWAP13,
|
|
..
|
|
} => {
|
|
block_stack.swap(13)?;
|
|
(vec![], None)
|
|
}
|
|
Instruction {
|
|
name: InstructionName::SWAP14,
|
|
..
|
|
} => {
|
|
block_stack.swap(14)?;
|
|
(vec![], None)
|
|
}
|
|
Instruction {
|
|
name: InstructionName::SWAP15,
|
|
..
|
|
} => {
|
|
block_stack.swap(15)?;
|
|
(vec![], None)
|
|
}
|
|
Instruction {
|
|
name: InstructionName::SWAP16,
|
|
..
|
|
} => {
|
|
block_stack.swap(16)?;
|
|
(vec![], None)
|
|
}
|
|
|
|
Instruction {
|
|
name: InstructionName::DUP1,
|
|
..
|
|
} => (vec![block_stack.dup(1)?], None),
|
|
Instruction {
|
|
name: InstructionName::DUP2,
|
|
..
|
|
} => (vec![block_stack.dup(2)?], None),
|
|
Instruction {
|
|
name: InstructionName::DUP3,
|
|
..
|
|
} => (vec![block_stack.dup(3)?], None),
|
|
Instruction {
|
|
name: InstructionName::DUP4,
|
|
..
|
|
} => (vec![block_stack.dup(4)?], None),
|
|
Instruction {
|
|
name: InstructionName::DUP5,
|
|
..
|
|
} => (vec![block_stack.dup(5)?], None),
|
|
Instruction {
|
|
name: InstructionName::DUP6,
|
|
..
|
|
} => (vec![block_stack.dup(6)?], None),
|
|
Instruction {
|
|
name: InstructionName::DUP7,
|
|
..
|
|
} => (vec![block_stack.dup(7)?], None),
|
|
Instruction {
|
|
name: InstructionName::DUP8,
|
|
..
|
|
} => (vec![block_stack.dup(8)?], None),
|
|
Instruction {
|
|
name: InstructionName::DUP9,
|
|
..
|
|
} => (vec![block_stack.dup(9)?], None),
|
|
Instruction {
|
|
name: InstructionName::DUP10,
|
|
..
|
|
} => (vec![block_stack.dup(10)?], None),
|
|
Instruction {
|
|
name: InstructionName::DUP11,
|
|
..
|
|
} => (vec![block_stack.dup(11)?], None),
|
|
Instruction {
|
|
name: InstructionName::DUP12,
|
|
..
|
|
} => (vec![block_stack.dup(12)?], None),
|
|
Instruction {
|
|
name: InstructionName::DUP13,
|
|
..
|
|
} => (vec![block_stack.dup(13)?], None),
|
|
Instruction {
|
|
name: InstructionName::DUP14,
|
|
..
|
|
} => (vec![block_stack.dup(14)?], None),
|
|
Instruction {
|
|
name: InstructionName::DUP15,
|
|
..
|
|
} => (vec![block_stack.dup(15)?], None),
|
|
Instruction {
|
|
name: InstructionName::DUP16,
|
|
..
|
|
} => (vec![block_stack.dup(16)?], None),
|
|
|
|
Instruction {
|
|
name:
|
|
InstructionName::PUSH
|
|
| InstructionName::PUSH1
|
|
| InstructionName::PUSH2
|
|
| InstructionName::PUSH3
|
|
| InstructionName::PUSH4
|
|
| InstructionName::PUSH5
|
|
| InstructionName::PUSH6
|
|
| InstructionName::PUSH7
|
|
| InstructionName::PUSH8
|
|
| InstructionName::PUSH9
|
|
| InstructionName::PUSH10
|
|
| InstructionName::PUSH11
|
|
| InstructionName::PUSH12
|
|
| InstructionName::PUSH13
|
|
| InstructionName::PUSH14
|
|
| InstructionName::PUSH15
|
|
| InstructionName::PUSH16
|
|
| InstructionName::PUSH17
|
|
| InstructionName::PUSH18
|
|
| InstructionName::PUSH19
|
|
| InstructionName::PUSH20
|
|
| InstructionName::PUSH21
|
|
| InstructionName::PUSH22
|
|
| InstructionName::PUSH23
|
|
| InstructionName::PUSH24
|
|
| InstructionName::PUSH25
|
|
| InstructionName::PUSH26
|
|
| InstructionName::PUSH27
|
|
| InstructionName::PUSH28
|
|
| InstructionName::PUSH29
|
|
| InstructionName::PUSH30
|
|
| InstructionName::PUSH31
|
|
| InstructionName::PUSH32,
|
|
value: Some(ref constant),
|
|
..
|
|
} => (
|
|
vec![num::BigUint::from_str_radix(
|
|
constant.as_str(),
|
|
revive_common::BASE_HEXADECIMAL,
|
|
)
|
|
.map(StackElement::Constant)?],
|
|
None,
|
|
),
|
|
Instruction {
|
|
name:
|
|
InstructionName::PUSH_ContractHash
|
|
| InstructionName::PUSH_ContractHashSize
|
|
| InstructionName::PUSHLIB,
|
|
value: Some(ref path),
|
|
..
|
|
} => (vec![StackElement::Path(path.to_owned())], None),
|
|
Instruction {
|
|
name: InstructionName::PUSH_Data,
|
|
value: Some(ref data),
|
|
..
|
|
} => (vec![StackElement::Data(data.to_owned())], None),
|
|
ref instruction @ Instruction {
|
|
name: InstructionName::PUSHDEPLOYADDRESS,
|
|
..
|
|
} => (
|
|
vec![StackElement::value(instruction.name.to_string())],
|
|
None,
|
|
),
|
|
|
|
ref instruction @ Instruction {
|
|
name: InstructionName::ADD,
|
|
..
|
|
} => {
|
|
let operands = &block_stack.elements[block_stack.elements.len() - 2..];
|
|
|
|
let result = match (&operands[1], &operands[0]) {
|
|
(Element::Tag(operand_1), Element::Constant(operand_2))
|
|
| (Element::Constant(operand_1), Element::Tag(operand_2))
|
|
| (Element::Tag(operand_1), Element::Tag(operand_2)) => {
|
|
match operand_1.checked_add(operand_2) {
|
|
Some(result) if Self::is_tag_value_valid(blocks, &result) => {
|
|
Element::Tag(result)
|
|
}
|
|
Some(_result) => Element::value(instruction.name.to_string()),
|
|
None => Element::value(instruction.name.to_string()),
|
|
}
|
|
}
|
|
(Element::Constant(operand_1), Element::Constant(operand_2)) => {
|
|
match operand_1.checked_add(operand_2) {
|
|
Some(result) => Element::Constant(result),
|
|
None => Element::value(instruction.name.to_string()),
|
|
}
|
|
}
|
|
_ => Element::value(instruction.name.to_string()),
|
|
};
|
|
|
|
(vec![result], None)
|
|
}
|
|
ref instruction @ Instruction {
|
|
name: InstructionName::SUB,
|
|
..
|
|
} => {
|
|
let operands = &block_stack.elements[block_stack.elements.len() - 2..];
|
|
|
|
let result = match (&operands[1], &operands[0]) {
|
|
(Element::Tag(operand_1), Element::Constant(operand_2))
|
|
| (Element::Constant(operand_1), Element::Tag(operand_2))
|
|
| (Element::Tag(operand_1), Element::Tag(operand_2)) => {
|
|
match operand_1.checked_sub(operand_2) {
|
|
Some(result) if Self::is_tag_value_valid(blocks, &result) => {
|
|
Element::Tag(result)
|
|
}
|
|
Some(_result) => Element::value(instruction.name.to_string()),
|
|
None => Element::value(instruction.name.to_string()),
|
|
}
|
|
}
|
|
(Element::Constant(operand_1), Element::Constant(operand_2)) => {
|
|
match operand_1.checked_sub(operand_2) {
|
|
Some(result) => Element::Constant(result),
|
|
None => Element::value(instruction.name.to_string()),
|
|
}
|
|
}
|
|
_ => Element::value(instruction.name.to_string()),
|
|
};
|
|
|
|
(vec![result], None)
|
|
}
|
|
ref instruction @ Instruction {
|
|
name: InstructionName::MUL,
|
|
..
|
|
} => {
|
|
let operands = &block_stack.elements[block_stack.elements.len() - 2..];
|
|
|
|
let result = match (&operands[1], &operands[0]) {
|
|
(Element::Tag(operand_1), Element::Constant(operand_2))
|
|
| (Element::Constant(operand_1), Element::Tag(operand_2))
|
|
| (Element::Tag(operand_1), Element::Tag(operand_2)) => {
|
|
match operand_1.checked_mul(operand_2) {
|
|
Some(result) if Self::is_tag_value_valid(blocks, &result) => {
|
|
Element::Tag(result)
|
|
}
|
|
Some(_result) => Element::value(instruction.name.to_string()),
|
|
None => Element::value(instruction.name.to_string()),
|
|
}
|
|
}
|
|
(Element::Constant(operand_1), Element::Constant(operand_2)) => {
|
|
match operand_1.checked_mul(operand_2) {
|
|
Some(result) => Element::Constant(result),
|
|
None => Element::value(instruction.name.to_string()),
|
|
}
|
|
}
|
|
_ => Element::value(instruction.name.to_string()),
|
|
};
|
|
|
|
(vec![result], None)
|
|
}
|
|
ref instruction @ Instruction {
|
|
name: InstructionName::DIV,
|
|
..
|
|
} => {
|
|
let operands = &block_stack.elements[block_stack.elements.len() - 2..];
|
|
|
|
let result = match (&operands[1], &operands[0]) {
|
|
(Element::Tag(operand_1), Element::Constant(operand_2))
|
|
| (Element::Constant(operand_1), Element::Tag(operand_2))
|
|
| (Element::Tag(operand_1), Element::Tag(operand_2)) => {
|
|
if operand_2.is_zero() {
|
|
Element::Tag(num::BigUint::zero())
|
|
} else {
|
|
match operand_1.checked_div(operand_2) {
|
|
Some(result) if Self::is_tag_value_valid(blocks, &result) => {
|
|
Element::Tag(result)
|
|
}
|
|
Some(_result) => Element::value(instruction.name.to_string()),
|
|
None => Element::value(instruction.name.to_string()),
|
|
}
|
|
}
|
|
}
|
|
(Element::Constant(operand_1), Element::Constant(operand_2)) => {
|
|
if operand_2.is_zero() {
|
|
Element::Constant(num::BigUint::zero())
|
|
} else {
|
|
match operand_1.checked_div(operand_2) {
|
|
Some(result) => Element::Constant(result),
|
|
None => Element::value(instruction.name.to_string()),
|
|
}
|
|
}
|
|
}
|
|
_ => Element::value(instruction.name.to_string()),
|
|
};
|
|
|
|
(vec![result], None)
|
|
}
|
|
ref instruction @ Instruction {
|
|
name: InstructionName::MOD,
|
|
..
|
|
} => {
|
|
let operands = &block_stack.elements[block_stack.elements.len() - 2..];
|
|
|
|
let result = match (&operands[1], &operands[0]) {
|
|
(Element::Tag(operand_1), Element::Constant(operand_2))
|
|
| (Element::Constant(operand_1), Element::Tag(operand_2))
|
|
| (Element::Tag(operand_1), Element::Tag(operand_2)) => {
|
|
if operand_2.is_zero() {
|
|
Element::Tag(num::BigUint::zero())
|
|
} else {
|
|
let result = operand_1 % operand_2;
|
|
if Self::is_tag_value_valid(blocks, &result) {
|
|
Element::Tag(result)
|
|
} else {
|
|
Element::value(instruction.name.to_string())
|
|
}
|
|
}
|
|
}
|
|
(Element::Constant(operand_1), Element::Constant(operand_2)) => {
|
|
if operand_2.is_zero() {
|
|
Element::Constant(num::BigUint::zero())
|
|
} else {
|
|
Element::Constant(operand_1 % operand_2)
|
|
}
|
|
}
|
|
_ => Element::value(instruction.name.to_string()),
|
|
};
|
|
|
|
(vec![result], None)
|
|
}
|
|
ref instruction @ Instruction {
|
|
name: InstructionName::SHL,
|
|
..
|
|
} => {
|
|
let operands = &block_stack.elements[block_stack.elements.len() - 2..];
|
|
|
|
let result = match (&operands[0], &operands[1]) {
|
|
(Element::Tag(tag), Element::Constant(offset)) => {
|
|
let offset = offset % revive_common::BIT_LENGTH_WORD;
|
|
let offset = offset.to_u64().expect("Always valid");
|
|
let result = tag << offset;
|
|
if Self::is_tag_value_valid(blocks, &result) {
|
|
Element::Tag(result)
|
|
} else {
|
|
Element::value(instruction.name.to_string())
|
|
}
|
|
}
|
|
(Element::Constant(constant), Element::Constant(offset)) => {
|
|
let offset = offset % revive_common::BIT_LENGTH_WORD;
|
|
let offset = offset.to_u64().expect("Always valid");
|
|
Element::Constant(constant << offset)
|
|
}
|
|
_ => Element::value(instruction.name.to_string()),
|
|
};
|
|
|
|
(vec![result], None)
|
|
}
|
|
ref instruction @ Instruction {
|
|
name: InstructionName::SHR,
|
|
..
|
|
} => {
|
|
let operands = &block_stack.elements[block_stack.elements.len() - 2..];
|
|
|
|
let result = match (&operands[0], &operands[1]) {
|
|
(Element::Tag(tag), Element::Constant(offset)) => {
|
|
let offset = offset % revive_common::BIT_LENGTH_WORD;
|
|
let offset = offset.to_u64().expect("Always valid");
|
|
let result = tag >> offset;
|
|
if Self::is_tag_value_valid(blocks, &result) {
|
|
Element::Tag(result)
|
|
} else {
|
|
Element::value(instruction.name.to_string())
|
|
}
|
|
}
|
|
(Element::Constant(constant), Element::Constant(offset)) => {
|
|
let offset = offset % revive_common::BIT_LENGTH_WORD;
|
|
let offset = offset.to_u64().expect("Always valid");
|
|
Element::Constant(constant >> offset)
|
|
}
|
|
_ => Element::value(instruction.name.to_string()),
|
|
};
|
|
|
|
(vec![result], None)
|
|
}
|
|
ref instruction @ Instruction {
|
|
name: InstructionName::OR,
|
|
..
|
|
} => {
|
|
let operands = &block_stack.elements[block_stack.elements.len() - 2..];
|
|
|
|
let result = match (&operands[1], &operands[0]) {
|
|
(Element::Tag(operand_1), Element::Tag(operand_2))
|
|
| (Element::Tag(operand_1), Element::Constant(operand_2))
|
|
| (Element::Constant(operand_1), Element::Tag(operand_2)) => {
|
|
let result = operand_1 | operand_2;
|
|
if Self::is_tag_value_valid(blocks, &result) {
|
|
Element::Tag(result)
|
|
} else {
|
|
Element::value(instruction.name.to_string())
|
|
}
|
|
}
|
|
(Element::Constant(operand_1), Element::Constant(operand_2)) => {
|
|
Element::Constant(operand_1 | operand_2)
|
|
}
|
|
_ => Element::value(instruction.name.to_string()),
|
|
};
|
|
|
|
(vec![result], None)
|
|
}
|
|
ref instruction @ Instruction {
|
|
name: InstructionName::XOR,
|
|
..
|
|
} => {
|
|
let operands = &block_stack.elements[block_stack.elements.len() - 2..];
|
|
|
|
let result = match (&operands[1], &operands[0]) {
|
|
(Element::Tag(operand_1), Element::Tag(operand_2))
|
|
| (Element::Tag(operand_1), Element::Constant(operand_2))
|
|
| (Element::Constant(operand_1), Element::Tag(operand_2)) => {
|
|
let result = operand_1 ^ operand_2;
|
|
if Self::is_tag_value_valid(blocks, &result) {
|
|
Element::Tag(result)
|
|
} else {
|
|
Element::value(instruction.name.to_string())
|
|
}
|
|
}
|
|
(Element::Constant(operand_1), Element::Constant(operand_2)) => {
|
|
Element::Constant(operand_1 ^ operand_2)
|
|
}
|
|
_ => Element::value(instruction.name.to_string()),
|
|
};
|
|
|
|
(vec![result], None)
|
|
}
|
|
ref instruction @ Instruction {
|
|
name: InstructionName::AND,
|
|
..
|
|
} => {
|
|
let operands = &block_stack.elements[block_stack.elements.len() - 2..];
|
|
|
|
let result = match (&operands[1], &operands[0]) {
|
|
(Element::Tag(operand_1), Element::Tag(operand_2))
|
|
| (Element::Tag(operand_1), Element::Constant(operand_2))
|
|
| (Element::Constant(operand_1), Element::Tag(operand_2)) => {
|
|
let result = operand_1 & operand_2;
|
|
if Self::is_tag_value_valid(blocks, &result) {
|
|
Element::Tag(result)
|
|
} else {
|
|
Element::value(instruction.name.to_string())
|
|
}
|
|
}
|
|
(Element::Constant(operand_1), Element::Constant(operand_2)) => {
|
|
Element::Constant(operand_1 & operand_2)
|
|
}
|
|
_ => Element::value(instruction.name.to_string()),
|
|
};
|
|
|
|
(vec![result], None)
|
|
}
|
|
|
|
ref instruction @ Instruction {
|
|
name: InstructionName::LT,
|
|
..
|
|
} => {
|
|
let operands = &block_stack.elements[block_stack.elements.len() - 2..];
|
|
|
|
let result = match (&operands[1], &operands[0]) {
|
|
(Element::Tag(operand_1), Element::Tag(operand_2)) => {
|
|
Element::Constant(num::BigUint::from(u64::from(operand_1 < operand_2)))
|
|
}
|
|
_ => Element::value(instruction.name.to_string()),
|
|
};
|
|
|
|
(vec![result], None)
|
|
}
|
|
ref instruction @ Instruction {
|
|
name: InstructionName::GT,
|
|
..
|
|
} => {
|
|
let operands = &block_stack.elements[block_stack.elements.len() - 2..];
|
|
|
|
let result = match (&operands[1], &operands[0]) {
|
|
(Element::Tag(operand_1), Element::Tag(operand_2)) => {
|
|
Element::Constant(num::BigUint::from(u64::from(operand_1 > operand_2)))
|
|
}
|
|
_ => Element::value(instruction.name.to_string()),
|
|
};
|
|
|
|
(vec![result], None)
|
|
}
|
|
ref instruction @ Instruction {
|
|
name: InstructionName::EQ,
|
|
..
|
|
} => {
|
|
let operands = &block_stack.elements[block_stack.elements.len() - 2..];
|
|
|
|
let result = match (&operands[1], &operands[0]) {
|
|
(Element::Tag(operand_1), Element::Tag(operand_2)) => {
|
|
Element::Constant(num::BigUint::from(u64::from(operand_1 == operand_2)))
|
|
}
|
|
_ => Element::value(instruction.name.to_string()),
|
|
};
|
|
|
|
(vec![result], None)
|
|
}
|
|
ref instruction @ Instruction {
|
|
name: InstructionName::ISZERO,
|
|
..
|
|
} => {
|
|
let operand = block_stack
|
|
.elements
|
|
.last()
|
|
.ok_or_else(|| anyhow::anyhow!("Operand is missing"))?;
|
|
|
|
let result = match operand {
|
|
Element::Tag(operand) => Element::Constant(if operand.is_zero() {
|
|
num::BigUint::one()
|
|
} else {
|
|
num::BigUint::zero()
|
|
}),
|
|
_ => Element::value(instruction.name.to_string()),
|
|
};
|
|
|
|
(vec![result], None)
|
|
}
|
|
|
|
ref instruction => (
|
|
vec![Element::value(instruction.name.to_string()); instruction.output_size()],
|
|
None,
|
|
),
|
|
};
|
|
|
|
Self::update_io_data(
|
|
block_stack,
|
|
block_element,
|
|
block_element.instruction.input_size(version),
|
|
stack_output,
|
|
)?;
|
|
|
|
if let Some(mut queue_element) = queue_element {
|
|
block_element.stack.clone_into(&mut queue_element.stack);
|
|
queue.push(queue_element);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Updates the stack data with input and output data.
|
|
fn update_io_data(
|
|
block_stack: &mut Stack,
|
|
block_element: &mut BlockElement,
|
|
input_size: usize,
|
|
output_data: Vec<Element>,
|
|
) -> anyhow::Result<()> {
|
|
if block_stack.len() < input_size {
|
|
anyhow::bail!("Stack underflow");
|
|
}
|
|
block_element.stack_input = Stack::new_with_elements(
|
|
block_stack
|
|
.elements
|
|
.drain(block_stack.len() - input_size..)
|
|
.collect(),
|
|
);
|
|
block_element.stack_output = Stack::new_with_elements(output_data);
|
|
block_stack.append(&mut block_element.stack_output.clone());
|
|
block_element.stack = block_stack.clone();
|
|
Ok(())
|
|
}
|
|
|
|
/// Handles the recursive function call.
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn handle_recursive_function_call(
|
|
recursive_function: &RecursiveFunction,
|
|
blocks: &HashMap<revive_llvm_context::PolkaVMFunctionBlockKey, Block>,
|
|
functions: &mut BTreeMap<revive_llvm_context::PolkaVMFunctionBlockKey, Self>,
|
|
extra_metadata: &ExtraMetadata,
|
|
visited_functions: &mut BTreeSet<VisitedElement>,
|
|
block_key: revive_llvm_context::PolkaVMFunctionBlockKey,
|
|
block_stack: &mut Stack,
|
|
block_element: &mut BlockElement,
|
|
version: &semver::Version,
|
|
) -> anyhow::Result<(revive_llvm_context::PolkaVMFunctionBlockKey, Vec<Element>)> {
|
|
let return_address_offset = block_stack.elements.len() - 2 - recursive_function.input_size;
|
|
let input_arguments_offset = return_address_offset + 1;
|
|
let callee_tag_offset = input_arguments_offset + recursive_function.input_size;
|
|
|
|
let return_address = match block_stack.elements[return_address_offset] {
|
|
Element::Tag(ref return_address) => revive_llvm_context::PolkaVMFunctionBlockKey::new(
|
|
block_key.code_type,
|
|
return_address.to_owned(),
|
|
),
|
|
ref element => anyhow::bail!("Expected the function return address, found {}", element),
|
|
};
|
|
let mut stack = Stack::with_capacity(1 + recursive_function.input_size);
|
|
stack.push(StackElement::ReturnAddress(
|
|
1 + recursive_function.output_size,
|
|
));
|
|
stack.append(&mut Stack::new_with_elements(
|
|
block_stack.elements[input_arguments_offset..callee_tag_offset].to_owned(),
|
|
));
|
|
let stack_hash = stack.hash();
|
|
|
|
let visited_element = VisitedElement::new(block_key.clone(), stack_hash);
|
|
if !visited_functions.contains(&visited_element) {
|
|
let mut function = Self::new(
|
|
version.to_owned(),
|
|
Type::new_recursive(
|
|
recursive_function.name.to_owned(),
|
|
block_key.clone(),
|
|
recursive_function.input_size,
|
|
recursive_function.output_size,
|
|
),
|
|
);
|
|
visited_functions.insert(visited_element);
|
|
function.traverse(blocks, functions, extra_metadata, visited_functions)?;
|
|
functions.insert(block_key.clone(), function);
|
|
}
|
|
|
|
let stack_output =
|
|
vec![Element::value("RETURN_VALUE".to_owned()); recursive_function.output_size];
|
|
let mut return_stack = Stack::new_with_elements(
|
|
block_stack.elements[..block_stack.len() - recursive_function.input_size - 2]
|
|
.to_owned(),
|
|
);
|
|
return_stack.append(&mut Stack::new_with_elements(stack_output.clone()));
|
|
let return_stack_hash = return_stack.hash();
|
|
|
|
block_element.instruction = Instruction::recursive_call(
|
|
recursive_function.name.to_owned(),
|
|
block_key,
|
|
return_stack_hash,
|
|
recursive_function.input_size + 2,
|
|
recursive_function.output_size,
|
|
return_address.clone(),
|
|
&block_element.instruction,
|
|
);
|
|
|
|
Ok((return_address, stack_output))
|
|
}
|
|
|
|
/// Pushes a block into the function.
|
|
fn insert_block(&mut self, mut block: Block) -> &mut Block {
|
|
let key = block.key.clone();
|
|
|
|
if let Some(entry) = self.blocks.get_mut(&key) {
|
|
if entry.iter().all(|existing_block| {
|
|
existing_block.initial_stack.hash() != block.initial_stack.hash()
|
|
}) {
|
|
block.instance = Some(entry.len());
|
|
entry.push(block);
|
|
}
|
|
} else {
|
|
block.instance = Some(0);
|
|
self.blocks.insert(block.key.clone(), vec![block]);
|
|
}
|
|
|
|
self.blocks
|
|
.get_mut(&key)
|
|
.expect("Always exists")
|
|
.last_mut()
|
|
.expect("Always exists")
|
|
}
|
|
|
|
/// Checks whether the tag value actually references an existing block.
|
|
/// Checks both deploy and runtime code.
|
|
fn is_tag_value_valid(
|
|
blocks: &HashMap<revive_llvm_context::PolkaVMFunctionBlockKey, Block>,
|
|
tag: &num::BigUint,
|
|
) -> bool {
|
|
blocks.contains_key(&revive_llvm_context::PolkaVMFunctionBlockKey::new(
|
|
revive_llvm_context::PolkaVMCodeType::Deploy,
|
|
tag & num::BigUint::from(u32::MAX),
|
|
)) || blocks.contains_key(&revive_llvm_context::PolkaVMFunctionBlockKey::new(
|
|
revive_llvm_context::PolkaVMCodeType::Runtime,
|
|
tag & num::BigUint::from(u32::MAX),
|
|
))
|
|
}
|
|
|
|
/// Finalizes the function data.
|
|
fn finalize(&mut self) {
|
|
for (_tag, blocks) in self.blocks.iter() {
|
|
for block in blocks.iter() {
|
|
for block_element in block.elements.iter() {
|
|
let total_length = block_element.stack.elements.len()
|
|
+ block_element.stack_input.len()
|
|
+ block_element.stack_output.len();
|
|
if total_length > self.stack_size {
|
|
self.stack_size = total_length;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<D> revive_llvm_context::PolkaVMWriteLLVM<D> for Function
|
|
where
|
|
D: revive_llvm_context::PolkaVMDependency + Clone,
|
|
{
|
|
fn declare(
|
|
&mut self,
|
|
context: &mut revive_llvm_context::PolkaVMContext<D>,
|
|
) -> anyhow::Result<()> {
|
|
let (function_type, output_size) = match self.r#type {
|
|
Type::Initial => {
|
|
let output_size = 0;
|
|
let r#type = context.function_type(
|
|
vec![context
|
|
.integer_type(revive_common::BIT_LENGTH_BOOLEAN)
|
|
.as_basic_type_enum()],
|
|
output_size,
|
|
false,
|
|
);
|
|
(r#type, output_size)
|
|
}
|
|
Type::Recursive {
|
|
input_size,
|
|
output_size,
|
|
..
|
|
} => {
|
|
let r#type = context.function_type(
|
|
vec![
|
|
context
|
|
.integer_type(revive_common::BIT_LENGTH_WORD)
|
|
.as_basic_type_enum();
|
|
input_size
|
|
],
|
|
output_size,
|
|
false,
|
|
);
|
|
(r#type, output_size)
|
|
}
|
|
};
|
|
let function = context.add_function(
|
|
self.name.as_str(),
|
|
function_type,
|
|
output_size,
|
|
Some(inkwell::module::Linkage::Private),
|
|
)?;
|
|
function
|
|
.borrow_mut()
|
|
.set_evmla_data(revive_llvm_context::PolkaVMFunctionEVMLAData::new(
|
|
self.stack_size,
|
|
));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn into_llvm(self, context: &mut revive_llvm_context::PolkaVMContext<D>) -> anyhow::Result<()> {
|
|
context.set_current_function(self.name.as_str())?;
|
|
|
|
for (key, blocks) in self.blocks.iter() {
|
|
for (index, block) in blocks.iter().enumerate() {
|
|
let inner = context.append_basic_block(format!("block_{key}/{index}").as_str());
|
|
let mut stack_hashes = vec![block.initial_stack.hash()];
|
|
stack_hashes.extend_from_slice(block.extra_hashes.as_slice());
|
|
let evmla_data =
|
|
revive_llvm_context::PolkaVMFunctionBlockEVMLAData::new(stack_hashes);
|
|
let mut block = revive_llvm_context::PolkaVMFunctionBlock::new(inner);
|
|
block.set_evmla_data(evmla_data);
|
|
context
|
|
.current_function()
|
|
.borrow_mut()
|
|
.evmla_mut()
|
|
.insert_block(key.to_owned(), block);
|
|
}
|
|
}
|
|
|
|
context.set_basic_block(context.current_function().borrow().entry_block());
|
|
let mut stack_variables = Vec::with_capacity(self.stack_size);
|
|
for stack_index in 0..self.stack_size {
|
|
let pointer = context.build_alloca(
|
|
context.word_type(),
|
|
format!("stack_var_{stack_index:03}").as_str(),
|
|
);
|
|
let value = match self.r#type {
|
|
Type::Recursive { input_size, .. }
|
|
if stack_index >= 1 && stack_index <= input_size =>
|
|
{
|
|
context
|
|
.current_function()
|
|
.borrow()
|
|
.declaration()
|
|
.value
|
|
.get_nth_param((stack_index - 1) as u32)
|
|
.expect("Always valid")
|
|
}
|
|
_ => context.word_const(0).as_basic_value_enum(),
|
|
};
|
|
context.build_store(pointer, value)?;
|
|
stack_variables.push(revive_llvm_context::PolkaVMArgument::new(
|
|
pointer.value.as_basic_value_enum(),
|
|
));
|
|
}
|
|
context.evmla_mut().stack = stack_variables;
|
|
|
|
match self.r#type {
|
|
Type::Initial => {
|
|
let is_deploy_code_flag = context
|
|
.current_function()
|
|
.borrow()
|
|
.get_nth_param(0)
|
|
.into_int_value();
|
|
let deploy_code_block = context.current_function().borrow().evmla().find_block(
|
|
&revive_llvm_context::PolkaVMFunctionBlockKey::new(
|
|
revive_llvm_context::PolkaVMCodeType::Deploy,
|
|
num::BigUint::zero(),
|
|
),
|
|
&Stack::default().hash(),
|
|
)?;
|
|
let runtime_code_block = context.current_function().borrow().evmla().find_block(
|
|
&revive_llvm_context::PolkaVMFunctionBlockKey::new(
|
|
revive_llvm_context::PolkaVMCodeType::Runtime,
|
|
num::BigUint::zero(),
|
|
),
|
|
&Stack::default().hash(),
|
|
)?;
|
|
context.build_conditional_branch(
|
|
is_deploy_code_flag,
|
|
deploy_code_block.inner(),
|
|
runtime_code_block.inner(),
|
|
)?;
|
|
}
|
|
Type::Recursive { ref block_key, .. } => {
|
|
let initial_block = context
|
|
.current_function()
|
|
.borrow()
|
|
.evmla()
|
|
.blocks
|
|
.get(block_key)
|
|
.expect("Always exists")
|
|
.first()
|
|
.expect("Always exists")
|
|
.inner();
|
|
context.build_unconditional_branch(initial_block);
|
|
}
|
|
}
|
|
|
|
for (key, blocks) in self.blocks.into_iter() {
|
|
for (llvm_block, ir_block) in context
|
|
.current_function()
|
|
.borrow()
|
|
.evmla()
|
|
.blocks
|
|
.get(&key)
|
|
.cloned()
|
|
.ok_or_else(|| anyhow::anyhow!("Undeclared function block {}", key))?
|
|
.into_iter()
|
|
.map(|block| block.inner())
|
|
.zip(blocks)
|
|
{
|
|
context.set_basic_block(llvm_block);
|
|
ir_block.into_llvm(context)?;
|
|
}
|
|
}
|
|
|
|
context.set_basic_block(context.current_function().borrow().return_block());
|
|
match context.current_function().borrow().r#return() {
|
|
revive_llvm_context::PolkaVMFunctionReturn::None => {
|
|
context.build_return(None);
|
|
}
|
|
revive_llvm_context::PolkaVMFunctionReturn::Primitive { pointer } => {
|
|
let return_value = context.build_load(pointer, "return_value")?;
|
|
context.build_return(Some(&return_value));
|
|
}
|
|
revive_llvm_context::PolkaVMFunctionReturn::Compound { pointer, .. } => {
|
|
let return_value = context.build_load(pointer, "return_value")?;
|
|
context.build_return(Some(&return_value));
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Display for Function {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self.r#type {
|
|
Type::Initial => writeln!(f, "function {} {{", self.name),
|
|
Type::Recursive {
|
|
input_size,
|
|
output_size,
|
|
..
|
|
} => writeln!(
|
|
f,
|
|
"function {}({}) -> {} {{",
|
|
self.name, input_size, output_size
|
|
),
|
|
}?;
|
|
writeln!(f, " stack_usage: {}", self.stack_size)?;
|
|
for (_key, blocks) in self.blocks.iter() {
|
|
for block in blocks.iter() {
|
|
write!(f, "{block}")?;
|
|
}
|
|
}
|
|
writeln!(f, "}}")?;
|
|
Ok(())
|
|
}
|
|
}
|