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:
Cyrill Leutwiler
2024-03-12 12:06:02 +01:00
committed by GitHub
parent d238d8f39e
commit cffa14a4d2
247 changed files with 35357 additions and 4905 deletions
-14
View File
@@ -1,14 +0,0 @@
[package]
name = "revive-cli"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
hex = { workspace = true }
evmil = { workspace = true }
revive-ir = { path = "../ir" }
revive-codegen = { path = "../codegen" }
revive-target-polkavm = { path = "../target-polkavm" }
-21
View File
@@ -1,21 +0,0 @@
use evmil::bytecode::Disassemble;
use revive_ir::cfg::BasicBlockFormatOption;
use revive_target_polkavm::PolkaVm;
fn main() {
let hexcode = std::fs::read_to_string(std::env::args().nth(1).unwrap()).unwrap();
let bytecode = hex::decode(hexcode.trim()).unwrap();
let instructions = bytecode.disassemble();
let mut ir = revive_ir::cfg::Program::new(&instructions);
ir.optimize();
ir.dot(BasicBlockFormatOption::Ir);
let target = PolkaVm::default();
let program = revive_codegen::program::Program::new(&target).unwrap();
program.emit(ir);
let artifact = program.compile_and_link();
std::fs::write("/tmp/out.pvm", artifact).unwrap();
}
-12
View File
@@ -1,12 +0,0 @@
[package]
name = "revive-codegen"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
inkwell = { workspace = true }
revive-compilation-target = { path = "../compilation-target" }
revive-ir = { path = "../ir" }
-2
View File
@@ -1,2 +0,0 @@
mod module;
pub mod program;
-37
View File
@@ -1,37 +0,0 @@
use inkwell::{
module::Module,
support::LLVMString,
targets::{RelocMode, TargetTriple},
};
use revive_compilation_target::target::Target;
pub(crate) fn create<'ctx, T>(target: &'ctx T) -> Result<Module<'ctx>, LLVMString>
where
T: Target<'ctx>,
{
let module = target.context().create_module("contract");
module.set_triple(&TargetTriple::create(<T as Target>::TARGET_TRIPLE));
module.set_source_file_name("contract.bin");
set_flags(target, &module);
for lib in target.libraries() {
module.link_in_module(lib)?;
}
Ok(module)
}
fn set_flags<'ctx, T>(target: &'ctx T, module: &Module<'ctx>)
where
T: Target<'ctx>,
{
if let RelocMode::PIC = <T as Target>::RELOC_MODE {
module.add_basic_value_flag(
"PIE Level",
inkwell::module::FlagBehavior::Override,
target.context().i32_type().const_int(2, false),
);
}
}
-93
View File
@@ -1,93 +0,0 @@
use inkwell::{
builder::Builder,
module::{Linkage, Module},
support::LLVMString,
targets::{FileType, TargetTriple},
values::{FunctionValue, GlobalValue},
AddressSpace,
};
use revive_compilation_target::environment::Environment;
use revive_compilation_target::target::Target;
use crate::module;
pub struct Program<'ctx, T> {
pub module: Module<'ctx>,
pub builder: Builder<'ctx>,
pub calldata: GlobalValue<'ctx>,
pub returndata: GlobalValue<'ctx>,
pub target: &'ctx T,
pub start: FunctionValue<'ctx>,
}
impl<'ctx, T> Program<'ctx, T>
where
T: Target<'ctx> + Environment<'ctx>,
{
pub fn new(target: &'ctx T) -> Result<Self, LLVMString> {
T::initialize_llvm();
let context = target.context();
let module = module::create(target)?;
let builder = context.create_builder();
let address_space = Some(AddressSpace::default());
let calldata_type = context.i8_type().array_type(T::CALLDATA_SIZE);
let calldata = module.add_global(calldata_type, address_space, "calldata");
let returndata_type = context.i8_type().array_type(T::RETURNDATA_SIZE);
let returndata = module.add_global(returndata_type, address_space, "returndata");
let start_fn_type = target.context().void_type().fn_type(&[], false);
let start = module.add_function("start", start_fn_type, Some(Linkage::Internal));
Ok(Self {
module,
builder,
calldata,
returndata,
target,
start,
})
}
pub fn emit(&self, program: revive_ir::cfg::Program) {
self.emit_start();
}
pub fn compile_and_link(&self) -> Vec<u8> {
inkwell::targets::Target::from_name(T::TARGET_NAME)
.expect("target name should be valid")
.create_target_machine(
&TargetTriple::create(T::TARGET_TRIPLE),
T::CPU,
T::TARGET_FEATURES,
self.target.optimization_level(),
T::RELOC_MODE,
T::CODE_MODEL,
)
.expect("target configuration should be valid")
.write_to_memory_buffer(&self.module, FileType::Object)
.map(|out| self.target.link(out.as_slice()))
.expect("linker should succeed")
.to_vec()
}
fn emit_start(&self) {
let start = self.start;
let block = self
.start
.get_last_basic_block()
.unwrap_or_else(|| self.target.context().append_basic_block(start, "entry"));
self.builder.position_at_end(block);
self.builder.build_return(None);
let env_start = self.target.call_start(&self.builder, self.start);
self.module
.link_in_module(env_start)
.expect("entrypoint module should be linkable");
}
}
@@ -1,15 +0,0 @@
use inkwell::{builder::Builder, module::Module, values::FunctionValue};
/// [Environment] describes EVM runtime functionality.
pub trait Environment<'ctx> {
const STACK_SIZE: u32 = 1024 * 32;
const CALLDATA_SIZE: u32 = 0x10000;
const RETURNDATA_SIZE: u32 = 0x10000;
const MEMORY_SIZE: u32 = 0x100000;
/// Build a module containing all required runtime exports and imports.
///
/// The `start` function is the entrypoint to the contract logic.
/// The returned `Module` is expected to call `start` somewhere.
fn call_start(&'ctx self, builder: &Builder<'ctx>, start: FunctionValue<'ctx>) -> Module<'ctx>;
}
-2
View File
@@ -1,2 +0,0 @@
pub mod environment;
pub mod target;
-27
View File
@@ -1,27 +0,0 @@
use inkwell::{
context::Context,
module::Module,
targets::{CodeModel, RelocMode},
OptimizationLevel,
};
pub trait Target<'ctx> {
const TARGET_NAME: &'ctx str;
const TARGET_TRIPLE: &'ctx str;
const TARGET_FEATURES: &'ctx str;
const CPU: &'ctx str;
const RELOC_MODE: RelocMode = RelocMode::Default;
const CODE_MODEL: CodeModel = CodeModel::Default;
fn initialize_llvm() {
inkwell::targets::Target::initialize_riscv(&Default::default());
}
fn context(&self) -> &Context;
fn libraries(&'ctx self) -> Vec<Module<'ctx>>;
fn link(&self, blob: &[u8]) -> Vec<u8>;
fn optimization_level(&self) -> OptimizationLevel;
}
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "revive-integration"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
hex = { workspace = true }
polkavm = { workspace = true }
parity-scale-codec = { workspace = true }
revive-solidity = { path = "../solidity" }
era-compiler-llvm-context = { path = "../llvm-context" }
alloy-primitives = { workspace = true }
@@ -0,0 +1,19 @@
contract Computation {
function triangle_number(int64 n) public pure returns (int64 sum) {
unchecked {
for (int64 x = 1; x <= n; x++) {
sum += x;
}
}
}
function odd_product(int32 n) public pure returns (int64) {
unchecked {
int64 prod = 1;
for (int32 x = 1; x <= n; x++) {
prod *= 2 * int64(x) - 1;
}
return prod;
}
}
}
+73
View File
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.0/contracts/token/ERC20/IERC20.sol
interface IERC20 {
function totalSupply() external view returns (uint);
function balanceOf(address account) external view returns (uint);
function transfer(address recipient, uint amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint);
function approve(address spender, uint amount) external returns (bool);
function transferFrom(
address sender,
address recipient,
uint amount
) external returns (bool);
event Transfer(address indexed from, address indexed to, uint value);
event Approval(address indexed owner, address indexed spender, uint value);
}
contract ERC20 is IERC20 {
uint public totalSupply;
mapping(address => uint) public balanceOf;
mapping(address => mapping(address => uint)) public allowance;
string public name = "Solidity by Example";
string public symbol = "SOLBYEX";
uint8 public decimals = 18;
function transfer(address recipient, uint amount) external returns (bool) {
balanceOf[msg.sender] -= amount;
balanceOf[recipient] += amount;
emit Transfer(msg.sender, recipient, amount);
return true;
}
function approve(address spender, uint amount) external returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transferFrom(
address sender,
address recipient,
uint amount
) external returns (bool) {
allowance[sender][msg.sender] -= amount;
balanceOf[sender] -= amount;
balanceOf[recipient] += amount;
emit Transfer(sender, recipient, amount);
return true;
}
function mint(uint amount) external {
balanceOf[msg.sender] += amount;
totalSupply += amount;
emit Transfer(address(0), msg.sender, amount);
}
function burn(uint amount) external {
balanceOf[msg.sender] -= amount;
totalSupply -= amount;
emit Transfer(msg.sender, address(0), amount);
}
}
+7
View File
@@ -0,0 +1,7 @@
contract Flipper {
bool coin;
function flip() public payable {
coin = !coin;
}
}
+95
View File
@@ -0,0 +1,95 @@
pub mod mock_runtime;
pub fn compile_blob(contract_name: &str, source_code: &str) -> Vec<u8> {
let file_name = "contract.sol";
let contracts = revive_solidity::test_utils::build_solidity(
[(file_name.into(), source_code.into())].into(),
Default::default(),
None,
revive_solidity::SolcPipeline::Yul,
era_compiler_llvm_context::OptimizerSettings::cycles(),
)
.expect("source should compile")
.contracts
.expect("source should contain at least one contract");
let bytecode = contracts[file_name][contract_name]
.evm
.as_ref()
.expect("source should produce EVM output")
.assembly_text
.as_ref()
.expect("source should produce assembly text");
hex::decode(bytecode).expect("hex encoding should always be valid")
}
#[cfg(test)]
mod tests {
use alloy_primitives::U256;
use crate::mock_runtime::{self, State};
#[test]
fn flipper() {
let code = crate::compile_blob("Flipper", include_str!("../contracts/flipper.sol"));
let state = State::new(0xcde4efa9u32.to_be_bytes().to_vec());
let (instance, export) = mock_runtime::prepare(&code);
let state = crate::mock_runtime::call(state, &instance, export);
assert_eq!(state.output.flags, 0);
assert_eq!(state.storage[&U256::ZERO], U256::try_from(1).unwrap());
let state = crate::mock_runtime::call(state, &instance, export);
assert_eq!(state.output.flags, 0);
assert_eq!(state.storage[&U256::ZERO], U256::ZERO);
}
#[test]
fn erc20() {
let _ = crate::compile_blob("ERC20", include_str!("../contracts/ERC20.sol"));
}
#[test]
fn triangle_number() {
let code = crate::compile_blob("Computation", include_str!("../contracts/Computation.sol"));
let param = alloy_primitives::U256::try_from(13).unwrap();
let expected = alloy_primitives::U256::try_from(91).unwrap();
// function triangle_number(int64)
let mut input = 0x0f760610u32.to_be_bytes().to_vec();
input.extend_from_slice(&param.to_be_bytes::<32>());
let state = State::new(input);
let (instance, export) = mock_runtime::prepare(&code);
let state = crate::mock_runtime::call(state, &instance, export);
assert_eq!(state.output.flags, 0);
let received =
alloy_primitives::U256::from_be_bytes::<32>(state.output.data.try_into().unwrap());
assert_eq!(received, expected);
}
#[test]
fn odd_product() {
let code = crate::compile_blob("Computation", include_str!("../contracts/Computation.sol"));
let param = alloy_primitives::I256::try_from(5i32).unwrap();
let expected = alloy_primitives::I256::try_from(945i64).unwrap();
// function odd_product(int32)
let mut input = 0x00261b66u32.to_be_bytes().to_vec();
input.extend_from_slice(&param.to_be_bytes::<32>());
let state = State::new(input);
let (instance, export) = mock_runtime::prepare(&code);
let state = crate::mock_runtime::call(state, &instance, export);
assert_eq!(state.output.flags, 0);
let received =
alloy_primitives::I256::from_be_bytes::<32>(state.output.data.try_into().unwrap());
assert_eq!(received, expected);
}
}
+204
View File
@@ -0,0 +1,204 @@
//! Mock environment used for integration tests.
//! TODO: Switch to drink! once RISCV is ready in polkadot-sdk
use std::collections::HashMap;
use alloy_primitives::U256;
use parity_scale_codec::Encode;
use polkavm::{
Caller, Config, Engine, ExportIndex, GasMeteringKind, InstancePre, Linker, Module,
ModuleConfig, ProgramBlob, Trap,
};
#[derive(Default, Clone, Debug)]
pub struct State {
pub input: Vec<u8>,
pub output: CallOutput,
pub value: u128,
pub storage: HashMap<U256, U256>,
}
#[derive(Clone, Debug)]
pub struct CallOutput {
pub flags: u32,
pub data: Vec<u8>,
}
impl Default for CallOutput {
fn default() -> Self {
Self {
flags: u32::MAX,
data: Vec::new(),
}
}
}
impl State {
pub fn new(input: Vec<u8>) -> Self {
Self {
input,
..Default::default()
}
}
pub fn reset_output(&mut self) {
self.output = Default::default();
}
pub fn assert_storage_key(&self, at: U256, expect: U256) {
assert_eq!(self.storage[&at], expect);
}
}
fn link_host_functions(engine: &Engine) -> Linker<State> {
let mut linker = Linker::new(engine);
linker
.func_wrap(
"input",
|caller: Caller<State>, out_ptr: u32, out_len_ptr: u32| -> Result<(), Trap> {
let (mut caller, state) = caller.split();
assert_ne!(0, caller.read_u32(out_len_ptr)?);
caller.write_memory(out_ptr, &state.input)?;
caller.write_memory(out_len_ptr, &(state.input.len() as u32).encode())?;
Ok(())
},
)
.unwrap();
linker
.func_wrap(
"seal_return",
|caller: Caller<State>, flags: u32, data_ptr: u32, data_len: u32| -> Result<(), Trap> {
let (caller, state) = caller.split();
state.output.flags = flags;
state.output.data = caller.read_memory_into_vec(data_ptr, data_len)?;
Err(Default::default())
},
)
.unwrap();
linker
.func_wrap(
"value_transferred",
|caller: Caller<State>, out_ptr: u32, out_len_ptr: u32| -> Result<(), Trap> {
let (mut caller, state) = caller.split();
let value = state.value.encode();
caller.write_memory(out_ptr, &value)?;
caller.write_memory(out_len_ptr, &(value.len() as u32).encode())?;
Ok(())
},
)
.unwrap();
linker
.func_wrap(
"debug_message",
|caller: Caller<State>, str_ptr: u32, str_len: u32| -> Result<u32, Trap> {
let (caller, _) = caller.split();
let data = caller.read_memory_into_vec(str_ptr, str_len)?;
print!("debug_message: {}", String::from_utf8(data).unwrap());
Ok(0)
},
)
.unwrap();
linker
.func_wrap(
"set_storage",
|caller: Caller<State>,
key_ptr: u32,
key_len: u32,
value_ptr: u32,
value_len: u32|
-> Result<u32, Trap> {
let (caller, state) = caller.split();
assert_eq!(key_len, 32, "storage key must be 32 bytes");
assert_eq!(value_len, 32, "storage value must be 32 bytes");
let key = caller.read_memory_into_vec(key_ptr, key_len)?;
let value = caller.read_memory_into_vec(value_ptr, value_len)?;
state.storage.insert(
U256::from_be_bytes::<32>(key.try_into().unwrap()),
U256::from_be_bytes::<32>(value.try_into().unwrap()),
);
Ok(0)
},
)
.unwrap();
linker
.func_wrap(
"get_storage",
|caller: Caller<State>,
key_ptr: u32,
key_len: u32,
out_ptr: u32,
out_len_ptr: u32|
-> Result<u32, Trap> {
let (mut caller, state) = caller.split();
let key = caller.read_memory_into_vec(key_ptr, key_len)?;
let out_len = caller.read_u32(out_len_ptr)?;
assert!(out_len >= 32);
let value = state
.storage
.get(&U256::from_be_bytes::<32>(key.try_into().unwrap()))
.map(U256::to_be_bytes::<32>)
.unwrap_or_default();
caller.write_memory(out_ptr, &value[..])?;
caller.write_memory(out_len_ptr, &32u32.to_le_bytes())?;
Ok(0)
},
)
.unwrap();
linker
}
pub fn prepare(code: &[u8]) -> (InstancePre<State>, ExportIndex) {
let blob = ProgramBlob::parse(code).unwrap();
let engine = Engine::new(&Config::new()).unwrap();
let mut module_config = ModuleConfig::new();
module_config.set_gas_metering(Some(GasMeteringKind::Sync));
let module = Module::from_blob(&engine, &module_config, &blob).unwrap();
let export = module.lookup_export("call").unwrap();
let func = link_host_functions(&engine)
.instantiate_pre(&module)
.unwrap();
(func, export)
}
pub fn call(mut state: State, on: &InstancePre<State>, export: ExportIndex) -> State {
state.reset_output();
let mut state_args = polkavm::StateArgs::default();
state_args.set_gas(polkavm::Gas::MAX);
let call_args = polkavm::CallArgs::new(&mut state, export);
match on.instantiate().unwrap().call(state_args, call_args) {
Err(polkavm::ExecutionError::Trap(_)) => state,
Err(other) => panic!("unexpected error: {other}"),
Ok(_) => panic!("unexpected return"),
}
}
-14
View File
@@ -1,14 +0,0 @@
[package]
name = "revive-ir"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
evmil = { workspace = true }
petgraph = { workspace = true }
primitive-types = { workspace = true }
indexmap = { workspace = true }
revive-compilation-target = { path = "../compilation-target" }
-58
View File
@@ -1,58 +0,0 @@
use primitive_types::U256;
#[derive(Clone, Copy)]
pub enum Kind {
Constant(U256),
Temporary(usize),
Stack,
}
#[derive(Clone, Copy)]
pub struct Address {
pub kind: Kind,
pub type_hint: Option<Type>,
}
impl From<(Kind, Option<Type>)> for Address {
fn from(value: (Kind, Option<Type>)) -> Self {
Self {
kind: value.0,
type_hint: value.1,
}
}
}
impl Address {
pub fn new(kind: Kind, type_hint: Option<Type>) -> Self {
Self { kind, type_hint }
}
}
#[derive(Clone, Copy)]
pub enum Type {
Int { size: u16 },
Bytes { size: u8 },
Bool,
}
impl Type {
pub fn int(size: u16) -> Self {
Self::Int { size }
}
fn bytes(size: u8) -> Self {
Self::Bytes { size }
}
}
impl Default for Type {
fn default() -> Self {
Type::Bytes { size: 32 }
}
}
pub enum LinearMemory {
CallData,
Memory,
ReturnData,
}
-60
View File
@@ -1,60 +0,0 @@
use indexmap::{IndexMap, IndexSet};
use petgraph::prelude::*;
use crate::{
analysis::BlockAnalysis,
cfg::{Branch, Program},
instruction::Instruction,
symbol::Kind,
};
/// Remove basic blocks not reachable from the start node.
#[derive(Default)]
pub struct ReachableCode(pub IndexSet<NodeIndex>);
impl BlockAnalysis for ReachableCode {
fn analyze_block(&mut self, node: NodeIndex, _program: &mut Program) {
self.0.insert(node);
}
fn apply_results(&mut self, program: &mut Program) {
program.cfg.graph.retain_nodes(|_, i| self.0.contains(&i));
}
}
/// Remove edges to the jump table if the jump target is statically known.
#[derive(Default)]
pub struct StaticJumps(IndexMap<EdgeIndex, (NodeIndex, NodeIndex)>);
impl BlockAnalysis for StaticJumps {
fn analyze_block(&mut self, node: NodeIndex, program: &mut Program) {
for edge in program.cfg.graph.edges(node) {
if *edge.weight() == Branch::Static {
continue;
}
if let Some(Instruction::ConditionalBranch { target, .. })
| Some(Instruction::UncoditionalBranch { target }) =
program.cfg.graph[node].instructions.last()
{
let Kind::Constant(bytecode_offset) = target.symbol().kind else {
continue;
};
let destination = program
.jump_targets
.get(&bytecode_offset.as_usize())
.unwrap_or(&program.cfg.invalid_jump);
self.0.insert(edge.id(), (node, *destination));
}
}
}
fn apply_results(&mut self, program: &mut Program) {
for (edge, (a, b)) in &self.0 {
program.cfg.graph.remove_edge(*edge);
program.cfg.graph.add_edge(*a, *b, Branch::Static);
}
}
}
-19
View File
@@ -1,19 +0,0 @@
use crate::{cfg::Program, instruction::Instruction, symbol::Type, POINTER_SIZE};
use petgraph::prelude::*;
use super::BlockAnalysis;
#[derive(Default)]
pub struct Unstack;
impl BlockAnalysis for Unstack {
fn analyze_block(&mut self, node: NodeIndex, program: &mut Program) {
for instruction in &program.cfg.graph[node].instructions {
match instruction {
_ => {}
}
}
}
fn apply_results(&mut self, _program: &mut Program) {}
}
-373
View File
@@ -1,373 +0,0 @@
use indexmap::{IndexMap, IndexSet};
use petgraph::prelude::*;
use crate::{
analysis::BlockAnalysis,
cfg::{Program, StackInfo},
instruction::{Instruction, Operator},
symbol::{Global, Kind, Symbol, SymbolBuilder, SymbolRef, SymbolTable},
};
#[derive(Default)]
pub struct IrBuilder;
impl BlockAnalysis for IrBuilder {
fn analyze_block(&mut self, node: NodeIndex, program: &mut Program) {
let mut builder = BlockBuilder::new(node, &mut program.symbol_table);
for opcode in &program.evm_instructions[program.cfg.graph[node].opcodes.to_owned()] {
builder.translate(&opcode.instruction);
}
let (instructions, stack_info) = builder.done();
program.cfg.graph[node].instructions = instructions;
program.cfg.graph[node].stack_info = stack_info;
}
fn apply_results(&mut self, _program: &mut Program) {}
}
pub struct BlockBuilder<'tbl> {
state: State<'tbl>,
instructions: Vec<Instruction>,
}
impl<'tbl> BlockBuilder<'tbl> {
fn new(node: NodeIndex, symbol_table: &'tbl mut SymbolTable) -> Self {
Self {
state: State::new(node, symbol_table),
instructions: Default::default(),
}
}
fn done(self) -> (Vec<Instruction>, StackInfo) {
let stack_info = StackInfo {
arguments: self.state.borrows,
generates: self.state.stack,
height: self.state.height,
};
assert_eq!(
stack_info.arguments as i32 + stack_info.height,
stack_info.generates.len() as i32,
"local stack elements must equal stack arguments taken + local height"
);
(self.instructions, stack_info)
}
fn translate(&mut self, opcode: &evmil::bytecode::Instruction) {
use evmil::bytecode::Instruction::*;
self.instructions.extend(match opcode {
JUMPDEST => Vec::new(),
PUSH(bytes) => {
self.state.push(Symbol::builder().constant(bytes));
Vec::new()
}
POP => {
self.state.pop();
Vec::new()
}
SWAP(n) => self.state.swap(*n as usize),
DUP(n) => vec![Instruction::Copy {
y: self.state.nth(*n as usize),
x: self.state.push(Symbol::builder().variable()),
}],
ADD => vec![Instruction::BinaryAssign {
y: self.state.pop(),
z: self.state.pop(),
x: self.state.push(Symbol::builder().variable()),
operator: Operator::Add,
}],
SUB => vec![Instruction::BinaryAssign {
y: self.state.pop(),
z: self.state.pop(),
x: self.state.push(Symbol::builder().variable()),
operator: Operator::Sub,
}],
MSTORE => vec![Instruction::IndexedAssign {
x: self.state.symbol_table.global(Global::Memory),
index: self.state.pop(),
y: self.state.pop(),
}],
MLOAD => vec![Instruction::IndexedCopy {
index: self.state.pop(),
x: self.state.push(Symbol::builder().variable()),
y: self.state.symbol_table.global(Global::Memory),
}],
JUMP => vec![Instruction::UncoditionalBranch {
target: self.state.pop(),
}],
JUMPI => vec![Instruction::ConditionalBranch {
target: self.state.pop(),
condition: self.state.pop(),
}],
CALLDATACOPY => vec![Instruction::Procedure {
symbol: Global::CallDataCopy,
parameters: vec![self.state.pop(), self.state.pop(), self.state.pop()],
}],
CALLDATALOAD => vec![Instruction::IndexedCopy {
index: self.state.pop(),
x: self.state.push(Symbol::builder().variable()),
y: self.state.symbol_table.global(Global::CallData),
}],
RETURN => vec![Instruction::Procedure {
symbol: Global::Return,
parameters: vec![self.state.pop(), self.state.pop()],
}],
GT => vec![Instruction::BinaryAssign {
y: self.state.pop(),
z: self.state.pop(),
x: self.state.push(Symbol::builder().variable()),
operator: Operator::GreaterThan,
}],
LT => vec![Instruction::BinaryAssign {
y: self.state.pop(),
z: self.state.pop(),
x: self.state.push(Symbol::builder().variable()),
operator: Operator::LessThan,
}],
EQ => vec![Instruction::BinaryAssign {
y: self.state.pop(),
z: self.state.pop(),
x: self.state.push(Symbol::builder().variable()),
operator: Operator::Equal,
}],
ISZERO => vec![Instruction::UnaryAssign {
y: self.state.pop(),
x: self.state.push(Symbol::builder().variable()),
operator: Operator::IsZero,
}],
_ => {
eprintln!("unimplement instruction: {opcode}");
Vec::new()
}
})
}
}
struct State<'tbl> {
node: NodeIndex,
symbol_table: &'tbl mut SymbolTable,
stack: Vec<SymbolRef>,
/// Every pop on an empty stack was counts as an additional argument.
borrows: usize,
/// Caches the arguments the block borrows from the stack.
arguments: IndexMap<usize, SymbolRef>,
/// Tracks the relative stack height:
/// - Pushes increase the height by one
/// - Pops decrease the height by one
height: i32,
}
impl<'tbl> State<'tbl> {
fn new(node: NodeIndex, symbol_table: &'tbl mut SymbolTable) -> Self {
Self {
node,
symbol_table,
stack: Default::default(),
borrows: Default::default(),
arguments: Default::default(),
height: Default::default(),
}
}
fn pop(&mut self) -> SymbolRef {
self.height -= 1;
self.stack.pop().unwrap_or_else(|| {
self.borrows += 1;
self.nth(0)
})
}
fn push(&mut self, builder: SymbolBuilder<(), Kind>) -> SymbolRef {
let symbol = builder.temporary().done();
let symbol = self.symbol_table.insert(self.node, symbol);
self.stack.push(symbol.clone());
self.height += 1;
symbol
}
fn swap(&mut self, n: usize) -> Vec<Instruction> {
// For free if both elements are local to the basic block
let top = self.stack.len().saturating_sub(1);
if n <= top {
self.stack.swap(top - n, top);
return Vec::new();
}
let tmp = self.symbol_table.temporary(self.node);
let a = self.nth(0);
let b = self.nth(n);
vec![
Instruction::Copy {
x: tmp.clone(),
y: a.clone(),
},
Instruction::Copy { x: a, y: b.clone() },
Instruction::Copy { x: b, y: tmp },
]
}
fn nth(&mut self, n: usize) -> SymbolRef {
self.stack
.iter()
.rev()
.nth(n)
.or_else(|| self.arguments.get(&(self.slot(n) as usize)))
.cloned()
.unwrap_or_else(|| {
let builder = Symbol::builder().stack(self.slot(n)).variable();
let symbol = self.symbol_table.insert(self.node, builder.done());
self.arguments.insert(self.slot(n) as usize, symbol.clone());
symbol
})
}
fn slot(&self, n: usize) -> i32 {
n as i32 - (self.stack.len() as i32 - self.borrows as i32)
}
}
#[cfg(test)]
mod tests {
use super::{BlockBuilder, State};
use crate::{
cfg::StackInfo,
instruction::Instruction,
symbol::{Symbol, SymbolTable},
};
use evmil::bytecode::Instruction::*;
fn translate<'tbl>(code: &[evmil::bytecode::Instruction]) -> (Vec<Instruction>, StackInfo) {
code.iter()
.fold(
BlockBuilder::new(Default::default(), &mut SymbolTable::default()),
|mut builder, instruction| {
builder.translate(instruction);
builder
},
)
.done()
}
#[test]
fn stack_slot_works() {
let mut symbol_table = SymbolTable::default();
let mut state = State::new(Default::default(), &mut symbol_table);
state.push(Symbol::builder().variable());
assert_eq!(state.slot(0), -1);
assert_eq!(state.slot(1), 0);
assert_eq!(state.slot(2), 1);
state.pop();
state.pop();
assert_eq!(state.slot(0), 1);
assert_eq!(state.slot(1), 2);
assert_eq!(state.slot(2), 3);
state.push(Symbol::builder().variable());
state.push(Symbol::builder().variable());
assert_eq!(state.slot(0), -1);
assert_eq!(state.slot(1), 0);
assert_eq!(state.slot(2), 1);
}
#[test]
fn push_works() {
let state = translate(&[PUSH(vec![1])]).1;
assert_eq!(state.height, 1);
assert_eq!(state.arguments, 0);
assert_eq!(state.generates.len(), 1);
}
#[test]
fn add_works() {
let state = translate(&[ADD]).1;
assert_eq!(state.height, -1);
assert_eq!(state.arguments, 2);
assert_eq!(state.generates.len(), 1);
}
#[test]
fn dup_works() {
let state = translate(&[DUP(4)]).1;
assert_eq!(state.height, 1);
assert_eq!(state.arguments, 0);
assert_eq!(state.generates.len(), 1);
}
#[test]
fn swap_works() {
let state = translate(&[SWAP(4)]).1;
assert_eq!(state.height, 0);
assert_eq!(state.arguments, 0);
assert_eq!(state.generates.len(), 0);
}
#[test]
fn jump() {
let state = translate(&[JUMP]).1;
assert_eq!(state.height, -1);
assert_eq!(state.arguments, 1);
assert_eq!(state.generates.len(), 0);
}
#[test]
fn pop5_push2() {
let state = translate(&[POP, POP, POP, POP, POP, PUSH(vec![1]), PUSH(vec![1])]).1;
assert_eq!(state.height, -3);
assert_eq!(state.arguments, 5);
assert_eq!(state.generates.len(), 2);
}
#[test]
fn fibonacci_loop_body() {
let state = translate(&[
PUSH(vec![1]),
ADD,
SWAP(2),
DUP(1),
SWAP(4),
ADD,
SWAP(2),
PUSH(vec![10]),
JUMP,
])
.1;
assert_eq!(state.height, 0);
assert_eq!(state.arguments, 1);
assert_eq!(state.generates.len(), 1);
}
}
-31
View File
@@ -1,31 +0,0 @@
use petgraph::prelude::*;
use crate::cfg::Program;
pub mod control_flow;
pub mod dominance;
pub mod evm_bytecode;
pub mod types;
/// The analyzer visits each basic block using DFS.
pub trait BlockAnalysis: Default {
fn analyze_block(&mut self, node: NodeIndex, program: &mut Program);
fn apply_results(&mut self, program: &mut Program);
}
pub fn analyze<Pass>(program: &mut Program) -> Pass
where
Pass: BlockAnalysis,
{
let mut dfs = Dfs::new(&program.cfg.graph, program.cfg.start);
let mut pass = Pass::default();
while let Some(node) = dfs.next(&program.cfg.graph) {
pass.analyze_block(node, program);
}
pass.apply_results(program);
pass
}
-10
View File
@@ -1,10 +0,0 @@
use indexmap::IndexMap;
use petgraph::prelude::*;
use crate::{
cfg::{Branch, Program},
instruction::Instruction,
symbol::Kind,
};
use super::BlockAnalysis;
-42
View File
@@ -1,42 +0,0 @@
use crate::{cfg::Program, instruction::Instruction, symbol::Type, POINTER_SIZE};
use petgraph::prelude::*;
use super::BlockAnalysis;
#[derive(Default)]
pub struct TypePropagation;
impl BlockAnalysis for TypePropagation {
fn analyze_block(&mut self, node: NodeIndex, program: &mut Program) {
for instruction in &program.cfg.graph[node].instructions {
match instruction {
Instruction::ConditionalBranch { condition, target } => {
condition.replace_type(Type::Bool);
target.replace_type(Type::Int(POINTER_SIZE));
}
Instruction::UncoditionalBranch { target } => {
target.replace_type(Type::Int(POINTER_SIZE));
}
Instruction::BinaryAssign { x, y, z, .. } => {
y.replace_type(x.symbol().type_hint);
z.replace_type(x.symbol().type_hint);
}
Instruction::Copy { x, y } | Instruction::UnaryAssign { x, y, .. } => {
x.replace_type(y.symbol().type_hint);
}
Instruction::IndexedCopy { index, .. }
| Instruction::IndexedAssign { index, .. } => {
index.replace_type(Type::Int(POINTER_SIZE))
}
_ => {}
}
}
}
fn apply_results(&mut self, _program: &mut Program) {}
}
-241
View File
@@ -1,241 +0,0 @@
use std::fmt::Write;
use std::ops::Range;
use indexmap::IndexMap;
use petgraph::dot::{Config, Dot};
use petgraph::prelude::*;
use crate::pass::dead_code::DeadCodeElimination;
use crate::pass::lift::BytecodeLifter;
use crate::pass::Pass;
use crate::symbol::SymbolRef;
use crate::{instruction::Instruction, symbol::SymbolTable};
pub struct Cfg {
pub graph: StableDiGraph<BasicBlock, Branch>,
pub start: NodeIndex,
pub jump_table: NodeIndex,
pub terminator: NodeIndex,
pub invalid_jump: NodeIndex,
}
#[derive(Debug, PartialEq)]
pub enum Branch {
Static,
Dynamic,
}
impl Default for Cfg {
fn default() -> Self {
let mut graph = StableDiGraph::new();
Self {
start: graph.add_node(Default::default()),
jump_table: graph.add_node(Default::default()),
terminator: graph.add_node(Default::default()),
invalid_jump: graph.add_node(Default::default()),
graph,
}
}
}
#[derive(Clone, Debug)]
pub struct EvmInstruction {
pub bytecode_offset: usize,
pub instruction: evmil::bytecode::Instruction,
}
#[derive(Debug, Default)]
pub struct BasicBlock {
pub opcodes: Range<usize>,
pub instructions: Vec<Instruction>,
pub stack_info: StackInfo,
}
#[derive(Debug, Default)]
pub struct StackInfo {
pub arguments: usize,
pub generates: Vec<SymbolRef>,
pub height: i32,
}
impl BasicBlock {
fn linear_at(start: usize) -> Self {
Self {
opcodes: start..start + 1,
..Default::default()
}
}
fn format(&self, evm_bytecode: &[EvmInstruction], options: BasicBlockFormatOption) -> String {
match options {
BasicBlockFormatOption::ByteCode => evm_bytecode[self.opcodes.start..self.opcodes.end]
.iter()
.fold(String::new(), |mut acc, opcode| {
writeln!(&mut acc, "{:?}", opcode.instruction).unwrap();
acc
}),
BasicBlockFormatOption::Ir => {
self.instructions
.iter()
.fold(String::new(), |mut acc, instruction| {
writeln!(&mut acc, "{instruction}").unwrap();
acc
})
}
_ => String::new(),
}
}
}
#[derive(Clone, Copy, Default)]
pub enum BasicBlockFormatOption {
ByteCode,
Ir,
#[default]
None,
}
pub struct Program {
pub evm_instructions: Vec<EvmInstruction>,
pub cfg: Cfg,
pub symbol_table: SymbolTable,
pub jump_targets: IndexMap<usize, NodeIndex>,
}
impl Program {
/// Create a new [Program] from EVM bytecode.
///
/// - Dynamic jumps reach the dynamic jump table
/// - `JUMPDEST` and `JUMPI` split up the node
/// - Instructions not returning reach the terminator node
pub fn new(bytecode: &[evmil::bytecode::Instruction]) -> Self {
let mut evm_instructions = Vec::with_capacity(bytecode.len());
let mut cfg = Cfg::default();
let mut jump_targets = IndexMap::default();
let mut bytecode_offset = 0;
let mut node = cfg.graph.add_node(Default::default());
cfg.graph.add_edge(cfg.start, node, Branch::Static);
cfg.graph
.add_edge(cfg.invalid_jump, cfg.terminator, Branch::Static);
cfg.graph
.add_edge(cfg.jump_table, cfg.invalid_jump, Branch::Dynamic);
for (index, opcode) in bytecode.iter().enumerate() {
evm_instructions.push(EvmInstruction {
bytecode_offset,
instruction: opcode.clone(),
});
cfg.graph[node].opcodes.end = index + 1;
use evmil::bytecode::Instruction::*;
match opcode {
// The preceding instruction did already split up control flow
JUMPDEST
if matches!(
evm_instructions[index.saturating_sub(1)].instruction,
JUMP | JUMPI | RETURN | REVERT | INVALID | STOP | SELFDESTRUCT
) =>
{
cfg.graph.add_edge(cfg.jump_table, node, Branch::Dynamic);
jump_targets.insert(bytecode_offset, node);
}
JUMPDEST => {
cfg.graph[node].opcodes.end = index;
let previous_node = node;
node = cfg.graph.add_node(BasicBlock::linear_at(index));
cfg.graph.add_edge(cfg.jump_table, node, Branch::Dynamic);
cfg.graph.add_edge(previous_node, node, Branch::Static);
jump_targets.insert(bytecode_offset, node);
}
JUMP => {
cfg.graph.add_edge(node, cfg.jump_table, Branch::Dynamic);
node = cfg.graph.add_node(BasicBlock::linear_at(index + 1));
}
JUMPI => {
cfg.graph.add_edge(node, cfg.jump_table, Branch::Dynamic);
let previous_node = node;
node = cfg.graph.add_node(BasicBlock::linear_at(index + 1));
cfg.graph.add_edge(previous_node, node, Branch::Static);
}
STOP | RETURN | REVERT | INVALID | SELFDESTRUCT => {
cfg.graph.add_edge(node, cfg.terminator, Branch::Static);
node = cfg.graph.add_node(BasicBlock::linear_at(index + 1));
}
_ => {}
}
bytecode_offset += opcode.length();
}
Self {
evm_instructions,
cfg,
symbol_table: Default::default(),
jump_targets,
}
}
pub fn optimize(&mut self) {
DeadCodeElimination::run(&mut Default::default(), self);
BytecodeLifter::run(&mut Default::default(), self);
DeadCodeElimination::run(&mut Default::default(), self)
}
pub fn dot(&self, format_options: BasicBlockFormatOption) {
let get_node_attrs = move |_, (index, node): (_, &BasicBlock)| {
let (color, shape, label) = if index == self.cfg.terminator {
("red", "oval", "Terminator".to_string())
} else if index == self.cfg.start {
("red", "oval", "Start".to_string())
} else if index == self.cfg.invalid_jump {
("blue", "hexagon", "Invalid jump target".to_string())
} else if index == self.cfg.jump_table {
("blue", "diamond", "Dynamic jump table".to_string())
} else {
let instructions = node.format(&self.evm_instructions, format_options);
let start = &self.evm_instructions[node.opcodes.start].bytecode_offset;
let end = &self
.evm_instructions
.get(node.opcodes.end)
.unwrap_or_else(|| &self.evm_instructions[node.opcodes.end - 1])
.bytecode_offset;
(
"black",
"rectangle",
format!("Bytecode (0x{start:02x}, 0x{end:02x}]\n---\n{instructions}",),
)
};
format!("color={color} shape={shape} label=\"{label}\"",)
};
let get_edge_attrs = |_, edge: petgraph::stable_graph::EdgeReference<'_, Branch>| {
let style = match edge.weight() {
Branch::Static => "solid",
Branch::Dynamic => "dashed",
};
format!("style={style}")
};
let dot = Dot::with_attr_getters(
&self.cfg.graph,
&[Config::EdgeNoLabel, Config::NodeNoLabel],
&get_edge_attrs,
&get_node_attrs,
);
println!("{dot:?}");
}
}
-143
View File
@@ -1,143 +0,0 @@
use crate::symbol::{Global, SymbolRef};
use std::fmt::Write;
#[derive(PartialEq, Debug)]
pub enum Instruction {
Nop,
/// `x = y op z`
BinaryAssign {
x: SymbolRef,
y: SymbolRef,
operator: Operator,
z: SymbolRef,
},
/// `x = op y`
UnaryAssign {
x: SymbolRef,
operator: Operator,
y: SymbolRef,
},
/// `branch target`
UncoditionalBranch {
target: SymbolRef,
},
/// `branch target if condition`
ConditionalBranch {
condition: SymbolRef,
target: SymbolRef,
},
/// `call(label, n)`
Procedure {
symbol: Global,
parameters: Vec<SymbolRef>,
},
/// `x = call(label, n)`
Function {
symbol: Global,
x: SymbolRef,
parameters: Vec<SymbolRef>,
},
/// `x = y`
Copy {
x: SymbolRef,
y: SymbolRef,
},
/// `x[index] = y`
IndexedAssign {
x: SymbolRef,
index: SymbolRef,
y: SymbolRef,
},
/// `x = y[index]`
IndexedCopy {
x: SymbolRef,
y: SymbolRef,
index: SymbolRef,
},
}
impl std::fmt::Display for Instruction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::BinaryAssign { x, y, operator, z } => write!(f, "{x} = {y} {operator:?} {z}"),
Self::UnaryAssign { x, operator, y } => write!(f, "{x} = {operator:?} {y} "),
Self::UncoditionalBranch { target } => write!(f, "branch {target}"),
Self::ConditionalBranch { condition, target } => {
write!(f, "if {condition} branch {target}")
}
Self::Procedure { symbol, parameters } => write!(
f,
"{symbol:?}({})",
parameters.iter().fold(String::new(), |mut acc, p| {
write!(&mut acc, "{p}, ").unwrap();
acc
})
),
Self::Function {
symbol,
x,
parameters: args,
} => write!(
f,
"{x} = {symbol:?}({})",
args.iter().fold(String::new(), |mut acc, p| {
write!(&mut acc, "{p}, ").unwrap();
acc
})
),
Self::Copy { x, y } => write!(f, "{x} = {y}"),
Self::IndexedAssign { x, index, y } => write!(f, "{x}[{index}] = {y}"),
Self::IndexedCopy { x, y, index } => write!(f, "{x} = {y}[{index}]"),
Self::Nop => write!(f, "no-op"),
}
}
}
#[derive(PartialEq, Debug)]
pub enum Operator {
Add,
Mul,
Sub,
Div,
SDiv,
Mod,
SMod,
AddMod,
MulMod,
Exp,
SignExtend,
LessThan,
GreaterThan,
SignedLessThan,
SignedGreaterThan,
Equal,
IsZero,
And,
Or,
Xor,
Not,
Byte,
ShiftLeft,
ShiftRight,
ShiftArithmeticRight,
}
-14
View File
@@ -1,14 +0,0 @@
pub mod analysis;
pub mod cfg;
pub mod instruction;
pub mod pass;
pub mod symbol;
pub static POINTER_SIZE: usize = 32;
#[cfg(test)]
mod tests {
#[test]
fn it_works() {}
}
-15
View File
@@ -1,15 +0,0 @@
use crate::{
analysis::{analyze, control_flow::ReachableCode},
cfg::Program,
};
use super::Pass;
#[derive(Default)]
pub struct DeadCodeElimination;
impl Pass for DeadCodeElimination {
fn run(&mut self, program: &mut Program) {
analyze::<ReachableCode>(program);
}
}
-19
View File
@@ -1,19 +0,0 @@
use crate::{
analysis::{
analyze, control_flow::StaticJumps, evm_bytecode::IrBuilder, types::TypePropagation,
},
cfg::Program,
};
use super::Pass;
#[derive(Default)]
pub struct BytecodeLifter;
impl Pass for BytecodeLifter {
fn run(&mut self, program: &mut Program) {
analyze::<IrBuilder>(program);
analyze::<StaticJumps>(program);
analyze::<TypePropagation>(program);
}
}
-8
View File
@@ -1,8 +0,0 @@
use crate::cfg::Program;
pub mod dead_code;
pub mod lift;
pub trait Pass: Default {
fn run(&mut self, program: &mut Program);
}
-317
View File
@@ -1,317 +0,0 @@
use indexmap::IndexMap;
use petgraph::prelude::NodeIndex;
use primitive_types::U256;
use std::{cell::RefCell, rc::Rc};
use crate::POINTER_SIZE;
#[derive(Debug, Default)]
pub struct SymbolTable {
table: IndexMap<NodeIndex, IndexMap<usize, Rc<RefCell<Symbol>>>>,
symbols: IndexMap<usize, Rc<RefCell<Symbol>>>,
global_scope: NodeIndex,
id_nonce: usize,
}
impl SymbolTable {
pub fn merge_scopes(&mut self, node: NodeIndex, target: NodeIndex) {
let sym = self.symbols.remove(&0).unwrap();
let new = self
.table
.get(&NodeIndex::default())
.unwrap()
.get(&0)
.unwrap();
//RefCell::replace(&sym, Rc::clone(new));
}
pub fn get_symbol(&self, id: usize) -> SymbolRef {
SymbolRef {
inner: self.symbols.get(&id).unwrap().clone(),
id,
}
}
pub fn insert(&mut self, scope: NodeIndex, symbol: Symbol) -> SymbolRef {
let id = self.next();
let inner = Rc::new(RefCell::new(symbol));
self.table
.entry(scope)
.or_default()
.insert(id, Rc::clone(&inner));
self.symbols.insert(id, inner.clone());
SymbolRef { inner, id }
}
pub fn global(&mut self, label: Global) -> SymbolRef {
self.table
.entry(self.global_scope)
.or_default()
.iter()
.find(|(_, symbol)| symbol.borrow().address == Address::Label(label))
.map(|(id, _)| *id)
.map(|id| self.get_symbol(id))
.unwrap_or_else(|| self.insert(self.global_scope, Symbol::builder().global(label)))
}
pub fn temporary(&mut self, node: NodeIndex) -> SymbolRef {
self.insert(node, Symbol::builder().temporary().variable().done())
}
fn next(&mut self) -> usize {
let current = self.id_nonce;
self.id_nonce += 1;
current
}
}
#[derive(Default)]
pub struct SymbolBuilder<A = (), K = ()> {
address: A,
type_hint: Type,
kind: K,
}
impl<K> SymbolBuilder<(), K> {
pub fn temporary(self) -> SymbolBuilder<Address, K> {
SymbolBuilder {
address: Address::Temporary,
type_hint: self.type_hint,
kind: self.kind,
}
}
pub fn stack(self, slot: i32) -> SymbolBuilder<Address, K> {
SymbolBuilder {
address: Address::Stack(slot),
type_hint: self.type_hint,
kind: self.kind,
}
}
pub fn global(self, label: Global) -> Symbol {
Symbol {
address: Address::Label(label),
type_hint: label.typ(),
kind: label.kind(),
}
}
}
impl<A> SymbolBuilder<A, ()> {
pub fn constant(self, bytes: &[u8]) -> SymbolBuilder<A, Kind> {
SymbolBuilder {
address: self.address,
type_hint: Type::Bytes(bytes.len()),
kind: Kind::Constant(U256::from_big_endian(bytes)),
}
}
pub fn variable(self) -> SymbolBuilder<A, Kind> {
SymbolBuilder {
address: self.address,
type_hint: self.type_hint,
kind: Kind::Variable,
}
}
}
impl<A, K> SymbolBuilder<A, K> {
pub fn of(self, type_hint: Type) -> Self {
Self { type_hint, ..self }
}
}
impl SymbolBuilder<Address, Kind> {
pub fn done(self) -> Symbol {
Symbol {
address: self.address,
type_hint: self.type_hint,
kind: self.kind,
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct Symbol {
pub address: Address,
pub type_hint: Type,
pub kind: Kind,
}
impl Symbol {
pub fn builder() -> SymbolBuilder {
Default::default()
}
}
#[derive(Clone, Debug)]
pub struct SymbolRef {
inner: Rc<RefCell<Symbol>>,
id: usize,
}
impl std::fmt::Display for SymbolRef {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let symbol = self.symbol();
let address = format!("${}_{}", self.id, symbol.address);
match symbol.kind {
Kind::Pointer => write!(f, "*{address}"),
Kind::Constant(value) => {
write!(f, "{} {address} := {value}", symbol.type_hint)
}
_ => write!(f, "{} {address} ", symbol.type_hint),
}
}
}
impl SymbolRef {
pub fn replace_type(&self, type_hint: Type) {
self.inner.replace_with(|old| Symbol {
address: old.address,
kind: old.kind,
type_hint,
});
}
pub fn symbol(&self) -> Symbol {
*self.inner.borrow()
}
pub fn id(&self) -> usize {
self.id
}
}
impl PartialEq for SymbolRef {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
#[derive(Default, Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum Address {
#[default]
Temporary,
Stack(i32),
Label(Global),
}
impl std::fmt::Display for Address {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Stack(slot) => write!(f, "stack[{slot}]"),
Self::Temporary => write!(f, "tmp"),
Self::Label(label) => write!(f, "{label:?}"),
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Default, Clone, Copy)]
pub enum Type {
#[default]
Word,
UInt(usize),
Int(usize),
Bytes(usize),
Bool,
}
impl std::fmt::Display for Type {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Word => write!(f, "word"),
Self::UInt(size) => write!(f, "u{}", size),
Self::Int(size) => write!(f, "i{}", size),
Self::Bytes(size) => write!(f, "bytes{size}"),
Self::Bool => write!(f, "bool"),
}
}
}
impl Type {
pub fn pointer() -> Self {
Self::UInt(POINTER_SIZE)
}
}
#[derive(Default, Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum Kind {
Pointer,
#[default]
Variable,
Constant(U256),
Function,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum Global {
Stack,
StackHeight,
CallData,
Memory,
ReturnData,
MemoryCopy,
// EVM runtime environment
Sha3,
Address,
CallDataLoad,
CallDataSize,
CallDataCopy,
CodeSize,
CodeCopy,
GasPrice,
ExtCodeSize,
ExtCodeCopy,
ReturnDataSize,
ReturnDataCopy,
ExtCodeHash,
BlockHash,
Coinbase,
Timestamp,
BlockNumber,
PrevRanDao,
GasLimit,
ChainId,
SelfBalance,
BaseFee,
SLoad,
SStore,
Gas,
Create,
Create2,
Call,
StaticCall,
DelegateCall,
CallCode,
Return,
Stop,
Revert,
SelfDestruct,
Event,
}
impl Global {
pub fn typ(&self) -> Type {
match self {
Self::Stack | Self::CallData | Self::Memory | Self::ReturnData => Type::pointer(),
Self::StackHeight => Type::UInt(POINTER_SIZE),
_ => Type::Word,
}
}
pub fn kind(&self) -> Kind {
match self {
Self::Stack | Self::CallData | Self::Memory | Self::ReturnData => Kind::Pointer,
Self::StackHeight => Kind::Variable,
_ => Kind::Function,
}
}
}
@@ -1,5 +1,5 @@
[package]
name = "revive-target-polkavm"
name = "revive-linker"
version = "0.1.0"
edition = "2021"
@@ -11,8 +11,7 @@ tempfile = { workspace = true }
polkavm-linker = { workspace = true }
polkavm-common = { workspace = true }
libc = { workspace = true }
anyhow = { workspace = true }
revive-codegen = { path = "../codegen" }
revive-compilation-target = { path = "../compilation-target" }
revive-builtins = { path = "../builtins" }
lld-sys = { path = "../lld-sys" }
@@ -1,25 +1,11 @@
use std::{ffi::CString, fs};
use std::{env, ffi::CString, fs};
use lld_sys::LLDELFLink;
use revive_builtins::COMPILER_RT;
const LINKER_SCRIPT: &str = r#"
SECTIONS {
. = 0x10000;
.rodata : { *(.rodata) *(.rodata.*) }
.data.rel.ro : { *(.data.rel.ro) *(.data.rel.ro.*) }
.got : { *(.got) *(.got.*) }
. = ALIGN(0x4000);
.data : { *(.sdata) *(.data) }
.bss : { *(.sbss) *(.bss) *(.bss.*) }
. = 0xf0000000;
.text : { KEEP(*(.text.polkavm_export)) *(.text .text.*) }
/DISCARD/ : { *(.eh_frame) }
. = ALIGN(4);
}"#;
fn invoke_lld(cmd_args: &[&str]) -> bool {
@@ -33,30 +19,33 @@ fn invoke_lld(cmd_args: &[&str]) -> bool {
unsafe { LLDELFLink(args.as_ptr(), args.len()) == 0 }
}
fn polkavm_linker(code: &[u8]) -> Vec<u8> {
fn polkavm_linker<T: AsRef<[u8]>>(code: T) -> anyhow::Result<Vec<u8>> {
let mut config = polkavm_linker::Config::default();
config.set_strip(true);
match polkavm_linker::program_from_elf(config, code) {
Ok(blob) => blob.as_bytes().to_vec(),
Err(reason) => panic!("polkavm linker failed: {}", reason),
}
polkavm_linker::program_from_elf(config, code.as_ref())
.map(|blob| blob.as_bytes().to_vec())
.map_err(|reason| anyhow::anyhow!("polkavm linker failed: {}", reason))
}
pub(crate) fn link(input: &[u8]) -> Vec<u8> {
pub fn link<T: AsRef<[u8]>>(input: T) -> anyhow::Result<Vec<u8>> {
let dir = tempfile::tempdir().expect("failed to create temp directory for linking");
let output_path = dir.path().join("out.so");
let object_path = dir.path().join("out.o");
let linker_script_path = dir.path().join("linker.ld");
let compiler_rt_path = dir.path().join("libclang_rt.builtins-riscv32.a");
fs::write(&object_path, input).unwrap_or_else(|msg| panic!("{msg} {object_path:?}"));
fs::write(&object_path, input).map_err(|msg| anyhow::anyhow!("{msg} {object_path:?}"))?;
if env::var("PVM_LINKER_DUMP_OBJ").is_ok() {
fs::copy(&object_path, "/tmp/out.o")?;
}
fs::write(&linker_script_path, LINKER_SCRIPT)
.unwrap_or_else(|msg| panic!("{msg} {linker_script_path:?}"));
.map_err(|msg| anyhow::anyhow!("{msg} {linker_script_path:?}"))?;
fs::write(&compiler_rt_path, COMPILER_RT)
.unwrap_or_else(|msg| panic!("{msg} {compiler_rt_path:?}"));
.map_err(|msg| anyhow::anyhow!("{msg} {compiler_rt_path:?}"))?;
let ld_args = [
"ld.lld",
@@ -75,12 +64,14 @@ pub(crate) fn link(input: &[u8]) -> Vec<u8> {
output_path.to_str().expect("should be utf8"),
];
assert!(!invoke_lld(&ld_args), "ld.lld failed");
if invoke_lld(&ld_args) {
return Err(anyhow::anyhow!("ld.lld failed"));
}
fs::copy(&object_path, "/tmp/out.o").unwrap();
fs::copy(&output_path, "/tmp/out.so").unwrap();
fs::copy(&linker_script_path, "/tmp/linkder.ld").unwrap();
if env::var("PVM_LINKER_DUMP_SO").is_ok() {
fs::copy(&output_path, "/tmp/out.so")?;
};
let blob = fs::read(&output_path).expect("ld.lld should produce output");
polkavm_linker(&blob)
let blob = fs::read(&output_path)?;
polkavm_linker(blob)
}
+34
View File
@@ -0,0 +1,34 @@
[package]
name = "era-compiler-llvm-context"
version = "1.4.1"
authors = [
"Oleksandr Zarudnyi <a.zarudnyy@matterlabs.dev>",
]
license = "MIT OR Apache-2.0"
edition = "2021"
description = "Shared front end code of the EraVM compilers"
[lib]
doctest = false
[dependencies]
anyhow = { workspace = true }
semver = { workspace = true }
itertools = { workspace = true }
serde = { workspace = true, features = ["derive"] }
regex = { workspace = true }
once_cell = { workspace = true }
num = { workspace = true }
hex = { workspace = true }
sha2 = { workspace = true }
sha3 = { workspace = true }
md5 = { workspace = true }
inkwell = { workspace = true }
zkevm_opcode_defs = { git = "https://github.com/matter-labs/era-zkevm_opcode_defs", branch = "v1.4.1" }
era-compiler-common = { git = "https://github.com/matter-labs/era-compiler-common", branch = "main" }
pallet-contracts-pvm-llapi = { path = "../pallet-contracts-pvm-llapi" }
revive-linker = { path = "../linker" }
revive-builtins = { path = "../builtins" }
revive-stdlib = { path = "../stdlib" }
@@ -0,0 +1,40 @@
//!
//! The debug IR type.
//!
///
/// The debug IR type.
///
#[allow(non_camel_case_types)]
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum IRType {
/// Whether to dump the Yul code.
Yul,
/// Whether to dump the EVM legacy assembly code.
EVMLA,
/// Whether to dump the Ethereal IR code.
EthIR,
/// Whether to dump the Vyper LLL IR code.
LLL,
/// Whether to dump the LLVM IR code.
LLVM,
/// Whether to dump the assembly code.
Assembly,
}
impl IRType {
///
/// Returns the file extension for the specified IR.
///
pub fn file_extension(&self) -> &'static str {
match self {
Self::Yul => era_compiler_common::EXTENSION_YUL,
Self::EthIR => era_compiler_common::EXTENSION_ETHIR,
Self::EVMLA => era_compiler_common::EXTENSION_EVMLA,
Self::LLL => era_compiler_common::EXTENSION_LLL,
Self::LLVM => era_compiler_common::EXTENSION_LLVM_SOURCE,
Self::Assembly => era_compiler_common::EXTENSION_ERAVM_ASSEMBLY,
}
}
}
+140
View File
@@ -0,0 +1,140 @@
//!
//! The debug configuration.
//!
pub mod ir_type;
use std::path::PathBuf;
use serde::Deserialize;
use serde::Serialize;
use self::ir_type::IRType;
///
/// The debug configuration.
///
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct DebugConfig {
/// The directory to dump the IRs to.
pub output_directory: PathBuf,
}
impl DebugConfig {
///
/// A shortcut constructor.
///
pub fn new(output_directory: PathBuf) -> Self {
Self { output_directory }
}
///
/// Dumps the Yul IR.
///
pub fn dump_yul(&self, contract_path: &str, code: &str) -> anyhow::Result<()> {
let mut file_path = self.output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, None, IRType::Yul);
file_path.push(full_file_name);
std::fs::write(file_path, code)?;
Ok(())
}
///
/// Dumps the EVM legacy assembly IR.
///
pub fn dump_evmla(&self, contract_path: &str, code: &str) -> anyhow::Result<()> {
let mut file_path = self.output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, None, IRType::EVMLA);
file_path.push(full_file_name);
std::fs::write(file_path, code)?;
Ok(())
}
///
/// Dumps the Ethereal IR.
///
pub fn dump_ethir(&self, contract_path: &str, code: &str) -> anyhow::Result<()> {
let mut file_path = self.output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, None, IRType::EthIR);
file_path.push(full_file_name);
std::fs::write(file_path, code)?;
Ok(())
}
///
/// Dumps the LLL IR.
///
pub fn dump_lll(&self, contract_path: &str, code: &str) -> anyhow::Result<()> {
let mut file_path = self.output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, None, IRType::LLL);
file_path.push(full_file_name);
std::fs::write(file_path, code)?;
Ok(())
}
///
/// Dumps the unoptimized LLVM IR.
///
pub fn dump_llvm_ir_unoptimized(
&self,
contract_path: &str,
module: &inkwell::module::Module,
) -> anyhow::Result<()> {
let llvm_code = module.print_to_string().to_string();
let mut file_path = self.output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, Some("unoptimized"), IRType::LLVM);
file_path.push(full_file_name);
std::fs::write(file_path, llvm_code)?;
Ok(())
}
///
/// Dumps the optimized LLVM IR.
///
pub fn dump_llvm_ir_optimized(
&self,
contract_path: &str,
module: &inkwell::module::Module,
) -> anyhow::Result<()> {
let llvm_code = module.print_to_string().to_string();
let mut file_path = self.output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, Some("optimized"), IRType::LLVM);
file_path.push(full_file_name);
std::fs::write(file_path, llvm_code)?;
Ok(())
}
///
/// Dumps the assembly.
///
pub fn dump_assembly(&self, contract_path: &str, code: &str) -> anyhow::Result<()> {
let mut file_path = self.output_directory.to_owned();
let full_file_name = Self::full_file_name(contract_path, None, IRType::Assembly);
file_path.push(full_file_name);
std::fs::write(file_path, code)?;
Ok(())
}
///
/// Creates a full file name, given the contract full path, suffix, and extension.
///
fn full_file_name(contract_path: &str, suffix: Option<&str>, ir_type: IRType) -> String {
let mut full_file_name = contract_path.replace('/', "_").replace(':', ".");
if let Some(suffix) = suffix {
full_file_name.push('.');
full_file_name.push_str(suffix);
}
full_file_name.push('.');
full_file_name.push_str(ir_type.file_extension());
full_file_name
}
}
+72
View File
@@ -0,0 +1,72 @@
//!
//! The LLVM context constants.
//!
/// The LLVM framework version.
pub const LLVM_VERSION: semver::Version = semver::Version::new(15, 0, 4);
/// The EraVM version.
pub const ZKEVM_VERSION: semver::Version = semver::Version::new(1, 3, 2);
/// The heap memory pointer pointer global variable name.
pub static GLOBAL_HEAP_MEMORY_POINTER: &str = "memory_pointer";
/// The calldata pointer global variable name.
pub static GLOBAL_CALLDATA_POINTER: &str = "ptr_calldata";
/// The calldata size global variable name.
pub static GLOBAL_CALLDATA_SIZE: &str = "calldatasize";
/// The return data pointer global variable name.
pub static GLOBAL_RETURN_DATA_POINTER: &str = "ptr_return_data";
/// The return data size pointer global variable name.
pub static GLOBAL_RETURN_DATA_SIZE: &str = "returndatasize";
/// The call flags global variable name.
pub static GLOBAL_CALL_FLAGS: &str = "call_flags";
/// The extra ABI data global variable name.
pub static GLOBAL_EXTRA_ABI_DATA: &str = "extra_abi_data";
/// The active pointer global variable name.
pub static GLOBAL_ACTIVE_POINTER: &str = "ptr_active";
/// The constant array global variable name prefix.
pub static GLOBAL_CONST_ARRAY_PREFIX: &str = "const_array_";
/// The global verbatim getter identifier prefix.
pub static GLOBAL_VERBATIM_GETTER_PREFIX: &str = "get_global::";
/// The external call data offset in the auxiliary heap.
pub const HEAP_AUX_OFFSET_EXTERNAL_CALL: u64 = 0;
/// The constructor return data offset in the auxiliary heap.
pub const HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA: u64 =
8 * (era_compiler_common::BYTE_LENGTH_FIELD as u64);
/// The number of the extra ABI data arguments.
pub const EXTRA_ABI_DATA_SIZE: usize = 0;
/// The `create` method deployer signature.
pub static DEPLOYER_SIGNATURE_CREATE: &str = "create(bytes32,bytes32,bytes)";
/// The `create2` method deployer signature.
pub static DEPLOYER_SIGNATURE_CREATE2: &str = "create2(bytes32,bytes32,bytes)";
/// The absence of system call bit.
pub const NO_SYSTEM_CALL_BIT: bool = false;
/// The system call bit.
pub const SYSTEM_CALL_BIT: bool = true;
///
/// The deployer call header size that consists of:
/// - selector (4 bytes)
/// - salt (32 bytes)
/// - bytecode hash (32 bytes)
/// - constructor arguments offset (32 bytes)
/// - constructor arguments length (32 bytes)
///
pub const DEPLOYER_CALL_HEADER_SIZE: usize =
era_compiler_common::BYTE_LENGTH_X32 + (era_compiler_common::BYTE_LENGTH_FIELD * 4);
@@ -0,0 +1,38 @@
//!
//! The address space aliases.
//!
///
/// The address space aliases.
///
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AddressSpace {
/// The stack memory.
Stack,
/// The heap memory.
Heap,
/// The auxiliary heap memory.
HeapAuxiliary,
/// The generic memory page.
Generic,
/// The code area.
Code,
/// The storage.
Storage,
/// The transient storage.
TransientStorage,
}
impl From<AddressSpace> for inkwell::AddressSpace {
fn from(value: AddressSpace) -> Self {
match value {
AddressSpace::Stack => Self::from(0),
AddressSpace::Heap => Self::from(1),
AddressSpace::HeapAuxiliary => Self::from(2),
AddressSpace::Generic => Self::from(3),
AddressSpace::Code => Self::from(4),
AddressSpace::Storage => Self::from(5),
AddressSpace::TransientStorage => Self::from(6),
}
}
}
@@ -0,0 +1,76 @@
//!
//! The LLVM argument with metadata.
//!
///
/// The LLVM argument with metadata.
///
#[derive(Debug, Clone)]
pub struct Argument<'ctx> {
/// The actual LLVM operand.
pub value: inkwell::values::BasicValueEnum<'ctx>,
/// The original AST value. Used mostly for string literals.
pub original: Option<String>,
/// The preserved constant value, if available.
pub constant: Option<num::BigUint>,
}
impl<'ctx> Argument<'ctx> {
/// The calldata offset argument index.
pub const ARGUMENT_INDEX_CALLDATA_OFFSET: usize = 0;
/// The calldata length argument index.
pub const ARGUMENT_INDEX_CALLDATA_LENGTH: usize = 1;
///
/// A shortcut constructor.
///
pub fn new(value: inkwell::values::BasicValueEnum<'ctx>) -> Self {
Self {
value,
original: None,
constant: None,
}
}
///
/// A shortcut constructor.
///
pub fn new_with_original(
value: inkwell::values::BasicValueEnum<'ctx>,
original: String,
) -> Self {
Self {
value,
original: Some(original),
constant: None,
}
}
///
/// A shortcut constructor.
///
pub fn new_with_constant(
value: inkwell::values::BasicValueEnum<'ctx>,
constant: num::BigUint,
) -> Self {
Self {
value,
original: None,
constant: Some(constant),
}
}
///
/// Returns the inner LLVM value.
///
pub fn to_llvm(&self) -> inkwell::values::BasicValueEnum<'ctx> {
self.value
}
}
impl<'ctx> From<inkwell::values::BasicValueEnum<'ctx>> for Argument<'ctx> {
fn from(value: inkwell::values::BasicValueEnum<'ctx>) -> Self {
Self::new(value)
}
}
@@ -0,0 +1,127 @@
//! The LLVM attribute.
use serde::Deserialize;
use serde::Serialize;
/// The LLVM attribute.
///
/// In order to check the real order in a new major version of LLVM, find the `Attribute.inc` file
/// inside of the LLVM build directory. This order is actually generated during the building.
///
/// FIXME: Generate this in build.rs?
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Attribute {
/// Unused (attributes start at 1).
Unused = 0,
AllocAlign = 1,
AllocatedPointer = 2,
AlwaysInline = 3,
Builtin = 4,
Cold = 5,
Convergent = 6,
DisableSanitizerInstrumentation = 7,
FnRetThunkExtern = 8,
Hot = 9,
ImmArg = 10,
InReg = 11,
InlineHint = 12,
JumpTable = 13,
MinSize = 14,
MustProgress = 15,
Naked = 16,
Nest = 17,
NoAlias = 18,
NoBuiltin = 19,
NoCallback = 20,
NoCapture = 21,
NoCfCheck = 22,
NoDuplicate = 23,
NoFree = 24,
NoImplicitFloat = 25,
NoInline = 26,
NoMerge = 27,
NoProfile = 28,
NoRecurse = 29,
NoRedZone = 30,
NoReturn = 31,
NoSanitizeBounds = 32,
NoSanitizeCoverage = 33,
NoSync = 34,
NoUndef = 35,
NoUnwind = 36,
NonLazyBind = 37,
NonNull = 38,
NullPointerIsValid = 39,
OptForFuzzing = 40,
OptimizeForSize = 41,
OptimizeNone = 42,
PresplitCoroutine = 43,
ReadNone = 44,
ReadOnly = 45,
Returned = 46,
ReturnsTwice = 47,
SExt = 48,
SafeStack = 49,
SanitizeAddress = 50,
SanitizeHWAddress = 51,
SanitizeMemTag = 52,
SanitizeMemory = 53,
SanitizeThread = 54,
ShadowCallStack = 55,
SkipProfile = 56,
Speculatable = 57,
SpeculativeLoadHardening = 58,
StackProtect = 59,
StackProtectReq = 60,
StackProtectStrong = 61,
StrictFP = 62,
SwiftAsync = 63,
SwiftError = 64,
SwiftSelf = 65,
WillReturn = 66,
WriteOnly = 67,
ZExt = 68,
// FirstTypeAttr = 69,
ByRef = 69,
ByVal = 70,
ElementType = 71,
InAlloca = 72,
Preallocated = 73,
StructRet = 74,
// LastTypeAttr = 74,
// FirstIntAttr = 75,
Alignment = 75,
AllocKind = 76,
AllocSize = 77,
Dereferenceable = 78,
DereferenceableOrNull = 79,
Memory = 80,
StackAlignment = 81,
UWTable = 82,
VScaleRange = 83,
// LastIntAttr = 83,
}
impl TryFrom<&str> for Attribute {
type Error = String;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"AlwaysInline" => Ok(Attribute::AlwaysInline),
"Cold" => Ok(Attribute::Cold),
"Hot" => Ok(Attribute::Hot),
"MinSize" => Ok(Attribute::MinSize),
"OptimizeForSize" => Ok(Attribute::OptimizeForSize),
"NoInline" => Ok(Attribute::NoInline),
"WillReturn" => Ok(Attribute::WillReturn),
"WriteOnly" => Ok(Attribute::WriteOnly),
"ReadNone" => Ok(Attribute::ReadNone),
"ReadOnly" => Ok(Attribute::ReadOnly),
"NoReturn" => Ok(Attribute::NoReturn),
// FIXME: Not in Attributes.inc
//"InaccessibleMemOnly" => Ok(Attribute::InaccessibleMemOnly),
"MustProgress" => Ok(Attribute::MustProgress),
_ => Err(value.to_owned()),
}
}
}
@@ -0,0 +1,45 @@
//!
//! The LLVM module build.
//!
use std::collections::BTreeMap;
use serde::Deserialize;
use serde::Serialize;
///
/// The LLVM module build.
///
#[derive(Debug, Serialize, Deserialize)]
pub struct Build {
/// The EraVM text assembly.
pub assembly_text: String,
/// The metadata hash.
pub metadata_hash: Option<[u8; era_compiler_common::BYTE_LENGTH_FIELD]>,
/// The EraVM binary bytecode.
pub bytecode: Vec<u8>,
/// The EraVM bytecode hash.
pub bytecode_hash: String,
/// The hash-to-full-path mapping of the contract factory dependencies.
pub factory_dependencies: BTreeMap<String, String>,
}
impl Build {
///
/// A shortcut constructor.
///
pub fn new(
assembly_text: String,
metadata_hash: Option<[u8; era_compiler_common::BYTE_LENGTH_FIELD]>,
bytecode: Vec<u8>,
bytecode_hash: String,
) -> Self {
Self {
assembly_text,
metadata_hash,
bytecode,
bytecode_hash,
factory_dependencies: BTreeMap::new(),
}
}
}
@@ -0,0 +1,26 @@
//!
//! The contract code types.
//!
///
/// The contract code types (deploy and runtime).
///
/// They do not represent any entities in the final bytecode, but this separation is always present
/// in the IRs used for translation to the EVM bytecode.
///
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum CodeType {
/// The deploy code.
Deploy,
/// The runtime code.
Runtime,
}
impl std::fmt::Display for CodeType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Deploy => write!(f, "deploy"),
Self::Runtime => write!(f, "runtime"),
}
}
}
@@ -0,0 +1,109 @@
//!
//! The LLVM debug information.
//!
use inkwell::debug_info::AsDIScope;
use num::Zero;
///
/// The LLVM debug information.
///
pub struct DebugInfo<'ctx> {
/// The compile unit.
compile_unit: inkwell::debug_info::DICompileUnit<'ctx>,
/// The debug info builder.
builder: inkwell::debug_info::DebugInfoBuilder<'ctx>,
}
impl<'ctx> DebugInfo<'ctx> {
///
/// A shortcut constructor.
///
pub fn new(module: &inkwell::module::Module<'ctx>) -> Self {
let (builder, compile_unit) = module.create_debug_info_builder(
true,
inkwell::debug_info::DWARFSourceLanguage::C,
module.get_name().to_string_lossy().as_ref(),
"",
"",
false,
"",
0,
"",
inkwell::debug_info::DWARFEmissionKind::Full,
0,
false,
false,
"",
"",
);
Self {
compile_unit,
builder,
}
}
///
/// Creates a function info.
///
pub fn create_function(
&self,
name: &str,
) -> anyhow::Result<inkwell::debug_info::DISubprogram<'ctx>> {
let subroutine_type = self.builder.create_subroutine_type(
self.compile_unit.get_file(),
Some(self.create_type(era_compiler_common::BIT_LENGTH_FIELD)?),
&[],
inkwell::debug_info::DIFlags::zero(),
);
let function = self.builder.create_function(
self.compile_unit.get_file().as_debug_info_scope(),
name,
None,
self.compile_unit.get_file(),
42,
subroutine_type,
true,
false,
1,
inkwell::debug_info::DIFlags::zero(),
false,
);
self.builder.create_lexical_block(
function.as_debug_info_scope(),
self.compile_unit.get_file(),
1,
1,
);
Ok(function)
}
///
/// Creates a primitive type info.
///
pub fn create_type(
&self,
bit_length: usize,
) -> anyhow::Result<inkwell::debug_info::DIType<'ctx>> {
self.builder
.create_basic_type(
"U256",
bit_length as u64,
0,
inkwell::debug_info::DIFlags::zero(),
)
.map(|basic_type| basic_type.as_type())
.map_err(|error| anyhow::anyhow!("Debug info error: {}", error))
}
///
/// Finalizes the builder.
///
pub fn finalize(&self) {
self.builder.finalize();
}
}
@@ -0,0 +1,34 @@
//!
//! The LLVM IR generator EVM legacy assembly data.
//!
use crate::eravm::context::argument::Argument;
///
/// The LLVM IR generator EVM legacy assembly data.
///
/// Describes some data that is only relevant to the EVM legacy assembly.
///
#[derive(Debug, Clone)]
pub struct EVMLAData<'ctx> {
/// The Solidity compiler version.
/// Some instruction behave differenly depending on the version.
pub version: semver::Version,
/// The static stack allocated for the current function.
pub stack: Vec<Argument<'ctx>>,
}
impl<'ctx> EVMLAData<'ctx> {
/// The default stack size.
pub const DEFAULT_STACK_SIZE: usize = 64;
///
/// A shortcut constructor.
///
pub fn new(version: semver::Version) -> Self {
Self {
version,
stack: Vec::with_capacity(Self::DEFAULT_STACK_SIZE),
}
}
}
@@ -0,0 +1,41 @@
//!
//! The LLVM IR generator function block key.
//!
use crate::eravm::context::code_type::CodeType;
///
/// The LLVM IR generator function block key.
///
/// Is only relevant to the EVM legacy assembly.
///
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Key {
/// The block code type.
pub code_type: CodeType,
/// The block tag.
pub tag: num::BigUint,
}
impl Key {
///
/// A shortcut constructor.
///
pub fn new(code_type: CodeType, tag: num::BigUint) -> Self {
Self { code_type, tag }
}
}
impl std::fmt::Display for Key {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}_{}",
match self.code_type {
CodeType::Deploy => "dt",
CodeType::Runtime => "rt",
},
self.tag
)
}
}
@@ -0,0 +1,25 @@
//!
//! The LLVM function block EVM legacy assembly data.
//!
pub mod key;
///
/// The LLVM function block EVM legacy assembly data.
///
/// Describes some data that is only relevant to the EVM legacy assembly.
///
#[derive(Debug, Clone)]
pub struct EVMLAData {
/// The initial hashes of the allowed stack states.
pub stack_hashes: Vec<md5::Digest>,
}
impl EVMLAData {
///
/// A shortcut constructor.
///
pub fn new(stack_hashes: Vec<md5::Digest>) -> Self {
Self { stack_hashes }
}
}
@@ -0,0 +1,68 @@
//!
//! The LLVM IR generator function block.
//!
pub mod evmla_data;
use self::evmla_data::EVMLAData;
///
/// The LLVM IR generator function block.
///
#[derive(Debug, Clone)]
pub struct Block<'ctx> {
/// The inner block.
inner: inkwell::basic_block::BasicBlock<'ctx>,
/// The EVM legacy assembly compiler data.
evmla_data: Option<EVMLAData>,
}
impl<'ctx> Block<'ctx> {
///
/// A shortcut constructor.
///
pub fn new(inner: inkwell::basic_block::BasicBlock<'ctx>) -> Self {
Self {
inner,
evmla_data: None,
}
}
///
/// Sets the EVM legacy assembly data.
///
pub fn set_evmla_data(&mut self, data: EVMLAData) {
self.evmla_data = Some(data);
}
///
/// The LLVM object reference.
///
pub fn inner(&self) -> inkwell::basic_block::BasicBlock<'ctx> {
self.inner
}
///
/// Returns the EVM data reference.
///
/// # Panics
/// If the EVM data has not been initialized.
///
pub fn evm(&self) -> &EVMLAData {
self.evmla_data
.as_ref()
.expect("The EVM data must have been initialized")
}
///
/// Returns the EVM data mutable reference.
///
/// # Panics
/// If the EVM data has not been initialized.
///
pub fn evm_mut(&mut self) -> &mut EVMLAData {
self.evmla_data
.as_mut()
.expect("The EVM data must have been initialized")
}
}
@@ -0,0 +1,26 @@
//!
//! The LLVM function declaration.
//!
///
/// The LLVM function declaration.
///
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Declaration<'ctx> {
/// The function type.
pub r#type: inkwell::types::FunctionType<'ctx>,
/// The function value.
pub value: inkwell::values::FunctionValue<'ctx>,
}
impl<'ctx> Declaration<'ctx> {
///
/// A shortcut constructor.
///
pub fn new(
r#type: inkwell::types::FunctionType<'ctx>,
value: inkwell::values::FunctionValue<'ctx>,
) -> Self {
Self { r#type, value }
}
}
@@ -0,0 +1,86 @@
//!
//! The LLVM function EVM legacy assembly data.
//!
use std::collections::BTreeMap;
use crate::eravm::context::function::block::evmla_data::key::Key as BlockKey;
use crate::eravm::context::function::block::Block;
///
/// The LLVM function EVM legacy assembly data.
///
/// Describes some data that is only relevant to the EVM legacy assembly.
///
#[derive(Debug)]
pub struct EVMLAData<'ctx> {
/// The ordinary blocks with numeric tags.
/// Is only used by the Solidity EVM compiler.
pub blocks: BTreeMap<BlockKey, Vec<Block<'ctx>>>,
/// The function stack size.
pub stack_size: usize,
}
impl<'ctx> EVMLAData<'ctx> {
///
/// A shortcut constructor.
///
pub fn new(stack_size: usize) -> Self {
Self {
blocks: BTreeMap::new(),
stack_size,
}
}
///
/// Inserts a function block.
///
pub fn insert_block(&mut self, key: BlockKey, block: Block<'ctx>) {
if let Some(blocks) = self.blocks.get_mut(&key) {
blocks.push(block);
} else {
self.blocks.insert(key, vec![block]);
}
}
///
/// Returns the block with the specified tag and initial stack pattern.
///
/// If there is only one block, it is returned unconditionally.
///
pub fn find_block(
&self,
key: &BlockKey,
stack_hash: &md5::Digest,
) -> anyhow::Result<Block<'ctx>> {
if self
.blocks
.get(key)
.ok_or_else(|| anyhow::anyhow!("Undeclared function block {}", key))?
.len()
== 1
{
return self
.blocks
.get(key)
.ok_or_else(|| anyhow::anyhow!("Undeclared function block {}", key))?
.first()
.cloned()
.ok_or_else(|| anyhow::anyhow!("Undeclared function block {}", key));
}
self.blocks
.get(key)
.ok_or_else(|| anyhow::anyhow!("Undeclared function block {}", key))?
.iter()
.find(|block| {
block
.evm()
.stack_hashes
.iter()
.any(|hash| hash == stack_hash)
})
.cloned()
.ok_or_else(|| anyhow::anyhow!("Undeclared function block {}", key))
}
}
@@ -0,0 +1,154 @@
//!
//! The LLVM intrinsic functions.
//!
use inkwell::types::BasicType;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::function::declaration::Declaration as FunctionDeclaration;
///
/// The LLVM intrinsic functions, implemented in the LLVM back-end.
///
/// Most of them are translated directly into bytecode instructions.
///
#[derive(Debug)]
pub struct Intrinsics<'ctx> {
/// The trap.
pub trap: FunctionDeclaration<'ctx>,
/// The memory copy within the heap.
pub memory_copy: FunctionDeclaration<'ctx>,
/// The memory copy from a generic page.
pub memory_copy_from_generic: FunctionDeclaration<'ctx>,
/// Performs endianness swaps on i256 values
pub byte_swap: FunctionDeclaration<'ctx>,
}
impl<'ctx> Intrinsics<'ctx> {
/// The corresponding intrinsic function name.
pub const FUNCTION_TRAP: &'static str = "llvm.trap";
/// The corresponding intrinsic function name.
pub const FUNCTION_MEMORY_COPY: &'static str = "llvm.memcpy.p1.p1.i256";
/// The corresponding intrinsic function name.
pub const FUNCTION_MEMORY_COPY_FROM_GENERIC: &'static str = "llvm.memcpy.p3.p1.i256";
/// The corresponding intrinsic function name.
pub const FUNCTION_BYTE_SWAP: &'static str = "llvm.bswap.i256";
///
/// A shortcut constructor.
///
pub fn new(
llvm: &'ctx inkwell::context::Context,
module: &inkwell::module::Module<'ctx>,
) -> Self {
let void_type = llvm.void_type();
let bool_type = llvm.bool_type();
let byte_type = llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_BYTE as u32);
let field_type = llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32);
let _stack_field_pointer_type = field_type.ptr_type(AddressSpace::Stack.into());
let heap_field_pointer_type = byte_type.ptr_type(AddressSpace::Heap.into());
let generic_byte_pointer_type = byte_type.ptr_type(AddressSpace::Generic.into());
let trap = Self::declare(
llvm,
module,
Self::FUNCTION_TRAP,
void_type.fn_type(&[], false),
);
let memory_copy = Self::declare(
llvm,
module,
Self::FUNCTION_MEMORY_COPY,
void_type.fn_type(
&[
heap_field_pointer_type.as_basic_type_enum().into(),
heap_field_pointer_type.as_basic_type_enum().into(),
field_type.as_basic_type_enum().into(),
bool_type.as_basic_type_enum().into(),
],
false,
),
);
let memory_copy_from_generic = Self::declare(
llvm,
module,
Self::FUNCTION_MEMORY_COPY_FROM_GENERIC,
void_type.fn_type(
&[
heap_field_pointer_type.as_basic_type_enum().into(),
generic_byte_pointer_type.as_basic_type_enum().into(),
field_type.as_basic_type_enum().into(),
bool_type.as_basic_type_enum().into(),
],
false,
),
);
let byte_swap = Self::declare(
llvm,
module,
Self::FUNCTION_BYTE_SWAP,
field_type.fn_type(&[field_type.as_basic_type_enum().into()], false),
);
Self {
trap,
memory_copy,
memory_copy_from_generic,
byte_swap,
}
}
///
/// Finds the specified LLVM intrinsic function in the target and returns its declaration.
///
pub fn declare(
llvm: &'ctx inkwell::context::Context,
module: &inkwell::module::Module<'ctx>,
name: &str,
r#type: inkwell::types::FunctionType<'ctx>,
) -> FunctionDeclaration<'ctx> {
let intrinsic = inkwell::intrinsics::Intrinsic::find(name)
.unwrap_or_else(|| panic!("Intrinsic function `{name}` does not exist"));
let argument_types = Self::argument_types(llvm, name);
let value = intrinsic
.get_declaration(module, argument_types.as_slice())
.unwrap_or_else(|| panic!("Intrinsic function `{name}` declaration error"));
FunctionDeclaration::new(r#type, value)
}
///
/// Returns the LLVM types for selecting via the signature.
///
pub fn argument_types(
llvm: &'ctx inkwell::context::Context,
name: &str,
) -> Vec<inkwell::types::BasicTypeEnum<'ctx>> {
let field_type = llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32);
match name {
name if name == Self::FUNCTION_MEMORY_COPY => vec![
field_type
.ptr_type(AddressSpace::Heap.into())
.as_basic_type_enum(),
field_type
.ptr_type(AddressSpace::Heap.into())
.as_basic_type_enum(),
field_type.as_basic_type_enum(),
],
name if name == Self::FUNCTION_MEMORY_COPY_FROM_GENERIC => vec![
field_type
.ptr_type(AddressSpace::Heap.into())
.as_basic_type_enum(),
field_type
.ptr_type(AddressSpace::Generic.into())
.as_basic_type_enum(),
field_type.as_basic_type_enum(),
],
name if name == Self::FUNCTION_BYTE_SWAP => vec![field_type.as_basic_type_enum()],
_ => vec![],
}
}
}
@@ -0,0 +1,758 @@
//!
//! The LLVM runtime functions.
//!
use inkwell::types::BasicType;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::attribute::Attribute;
use crate::eravm::context::function::declaration::Declaration as FunctionDeclaration;
use crate::eravm::context::function::Function;
use crate::optimizer::Optimizer;
///
/// The runtime functions, implemented on the LLVM side.
///
/// The functions are automatically linked to the LLVM implementations if the signatures match.
///
#[derive(Debug)]
pub struct LLVMRuntime<'ctx> {
/// The LLVM personality function, used for exception handling.
pub personality: FunctionDeclaration<'ctx>,
/// The LLVM exception throwing function.
pub cxa_throw: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub div: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub sdiv: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub r#mod: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub smod: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub shl: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub shr: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub sar: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub byte: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub add_mod: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub mul_mod: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub exp: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub sign_extend: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub mstore8: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub sha3: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub system_request: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
//pub far_call: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub far_call_byref: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub static_call: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub static_call_byref: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub delegate_call: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub delegate_call_byref: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub mimic_call: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub mimic_call_byref: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub r#return: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub revert: FunctionDeclaration<'ctx>,
}
impl<'ctx> LLVMRuntime<'ctx> {
/// The LLVM personality function name.
pub const FUNCTION_PERSONALITY: &'static str = "__personality";
/// The LLVM exception throwing function name.
pub const FUNCTION_CXA_THROW: &'static str = "__cxa_throw";
/// The corresponding runtime function name.
pub const FUNCTION_DIV: &'static str = "__div";
/// The corresponding runtime function name.
pub const FUNCTION_SDIV: &'static str = "__sdiv";
/// The corresponding runtime function name.
pub const FUNCTION_MOD: &'static str = "__mod";
/// The corresponding runtime function name.
pub const FUNCTION_SMOD: &'static str = "__smod";
/// The corresponding runtime function name.
pub const FUNCTION_SHL: &'static str = "__shl";
/// The corresponding runtime function name.
pub const FUNCTION_SHR: &'static str = "__shr";
/// The corresponding runtime function name.
pub const FUNCTION_SAR: &'static str = "__sar";
/// The corresponding runtime function name.
pub const FUNCTION_BYTE: &'static str = "__byte";
/// The corresponding runtime function name.
pub const FUNCTION_ADDMOD: &'static str = "__addmod";
/// The corresponding runtime function name.
pub const FUNCTION_MULMOD: &'static str = "__mulmod";
/// The corresponding runtime function name.
pub const FUNCTION_EXP: &'static str = "__exp";
/// The corresponding runtime function name.
pub const FUNCTION_SIGNEXTEND: &'static str = "__signextend";
/// The corresponding runtime function name.
pub const FUNCTION_MSTORE8: &'static str = "__mstore8";
/// The corresponding runtime function name.
pub const FUNCTION_SHA3: &'static str = "__sha3";
/// The corresponding runtime function name.
pub const FUNCTION_SYSTEM_REQUEST: &'static str = "__system_request";
/// The corresponding runtime function name.
pub const FUNCTION_FARCALL: &'static str = "__farcall";
/// The corresponding runtime function name.
pub const FUNCTION_STATICCALL: &'static str = "__staticcall";
/// The corresponding runtime function name.
pub const FUNCTION_DELEGATECALL: &'static str = "__delegatecall";
/// The corresponding runtime function name.
pub const FUNCTION_MIMICCALL: &'static str = "__mimiccall";
/// The corresponding runtime function name.
pub const FUNCTION_FARCALL_BYREF: &'static str = "__farcall_byref";
/// The corresponding runtime function name.
pub const FUNCTION_STATICCALL_BYREF: &'static str = "__staticcall_byref";
/// The corresponding runtime function name.
pub const FUNCTION_DELEGATECALL_BYREF: &'static str = "__delegatecall_byref";
/// The corresponding runtime function name.
pub const FUNCTION_MIMICCALL_BYREF: &'static str = "__mimiccall_byref";
/// The corresponding runtime function name.
pub const FUNCTION_RETURN: &'static str = "__return";
/// The corresponding runtime function name.
pub const FUNCTION_REVERT: &'static str = "__revert";
///
/// A shortcut constructor.
///
pub fn new(
llvm: &'ctx inkwell::context::Context,
module: &inkwell::module::Module<'ctx>,
optimizer: &Optimizer,
) -> Self {
let personality = Self::declare(
module,
Self::FUNCTION_PERSONALITY,
llvm.i32_type().fn_type(&[], false),
None,
);
let cxa_throw = Self::declare(
module,
Self::FUNCTION_CXA_THROW,
llvm.void_type().fn_type(
vec![
llvm.i8_type()
.ptr_type(AddressSpace::Stack.into())
.as_basic_type_enum()
.into();
3
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_cxa_throw_attributes(llvm, cxa_throw);
let div = Self::declare(
module,
Self::FUNCTION_DIV,
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into();
2
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, div, optimizer);
Function::set_pure_function_attributes(llvm, div);
let r#mod = Self::declare(
module,
Self::FUNCTION_MOD,
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into();
2
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, r#mod, optimizer);
Function::set_pure_function_attributes(llvm, r#mod);
let sdiv = Self::declare(
module,
Self::FUNCTION_SDIV,
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into();
2
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, sdiv, optimizer);
Function::set_pure_function_attributes(llvm, sdiv);
let smod = Self::declare(
module,
Self::FUNCTION_SMOD,
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into();
2
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, smod, optimizer);
Function::set_pure_function_attributes(llvm, smod);
let shl = Self::declare(
module,
Self::FUNCTION_SHL,
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into();
2
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, shl, optimizer);
Function::set_pure_function_attributes(llvm, shl);
let shr = Self::declare(
module,
Self::FUNCTION_SHR,
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into();
2
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, shr, optimizer);
Function::set_pure_function_attributes(llvm, shr);
let sar = Self::declare(
module,
Self::FUNCTION_SAR,
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into();
2
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, sar, optimizer);
Function::set_pure_function_attributes(llvm, sar);
let byte = Self::declare(
module,
Self::FUNCTION_BYTE,
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into();
2
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, byte, optimizer);
Function::set_pure_function_attributes(llvm, byte);
let add_mod = Self::declare(
module,
Self::FUNCTION_ADDMOD,
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into();
3
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, add_mod, optimizer);
Function::set_pure_function_attributes(llvm, add_mod);
let mul_mod = Self::declare(
module,
Self::FUNCTION_MULMOD,
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into();
3
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, mul_mod, optimizer);
Function::set_pure_function_attributes(llvm, mul_mod);
let exp = Self::declare(
module,
Self::FUNCTION_EXP,
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into();
2
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, exp, optimizer);
Function::set_pure_function_attributes(llvm, exp);
let sign_extend = FunctionDeclaration::new(
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into();
2
]
.as_slice(),
false,
),
module
.get_function(Self::FUNCTION_SIGNEXTEND)
.expect("should be declared in stdlib"),
);
Function::set_default_attributes(llvm, sign_extend, optimizer);
Function::set_pure_function_attributes(llvm, sign_extend);
let mstore8 = Self::declare(
module,
Self::FUNCTION_MSTORE8,
llvm.void_type().fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_BYTE as u32)
.ptr_type(AddressSpace::Heap.into())
.as_basic_type_enum()
.into(),
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into(),
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, mstore8, optimizer);
Function::set_attributes(
llvm,
mstore8,
vec![
Attribute::MustProgress,
Attribute::NoUnwind,
Attribute::WillReturn,
],
false,
);
let sha3 = Self::declare(
module,
Self::FUNCTION_SHA3,
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_BYTE as u32)
.ptr_type(AddressSpace::Heap.into())
.as_basic_type_enum()
.into(),
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into(),
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_BOOLEAN as u32)
.as_basic_type_enum()
.into(),
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, sha3, optimizer);
Function::set_attributes(
llvm,
sha3,
//vec![Attribute::ArgMemOnly, Attribute::ReadOnly],
vec![],
false,
);
let system_request = Self::declare(
module,
Self::FUNCTION_SYSTEM_REQUEST,
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into(),
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into(),
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into(),
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.ptr_type(AddressSpace::Stack.into())
.as_basic_type_enum()
.into(),
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, system_request, optimizer);
Function::set_attributes(
llvm,
system_request,
//vec![Attribute::ArgMemOnly, Attribute::ReadOnly],
vec![],
false,
);
let external_call_arguments: Vec<inkwell::types::BasicMetadataTypeEnum> = vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into();
crate::eravm::context::function::runtime::entry::Entry::MANDATORY_ARGUMENTS_COUNT
+ crate::eravm::EXTRA_ABI_DATA_SIZE
];
let mut mimic_call_arguments = external_call_arguments.clone();
mimic_call_arguments.push(
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into(),
);
let mut external_call_arguments_by_ref: Vec<inkwell::types::BasicMetadataTypeEnum> = vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_BYTE as u32)
.ptr_type(AddressSpace::Generic.into())
.as_basic_type_enum()
.into(),
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into(),
];
external_call_arguments_by_ref.extend::<Vec<inkwell::types::BasicMetadataTypeEnum>>(vec![
llvm.custom_width_int_type(
era_compiler_common::BIT_LENGTH_FIELD as u32
)
.as_basic_type_enum()
.into();
crate::eravm::EXTRA_ABI_DATA_SIZE
]);
let mut mimic_call_arguments_by_ref = external_call_arguments_by_ref.clone();
mimic_call_arguments_by_ref.push(
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into(),
);
let external_call_result_type = llvm
.struct_type(
&[
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_BYTE as u32)
.ptr_type(AddressSpace::Generic.into())
.as_basic_type_enum(),
llvm.bool_type().as_basic_type_enum(),
],
false,
)
.as_basic_type_enum();
//let far_call = Self::declare(
// module,
// Self::FUNCTION_FARCALL,
// external_call_result_type.fn_type(external_call_arguments.as_slice(), false),
// Some(inkwell::module::Linkage::External),
//);
//Function::set_default_attributes(llvm, far_call, optimizer);
let static_call = Self::declare(
module,
Self::FUNCTION_STATICCALL,
external_call_result_type.fn_type(external_call_arguments.as_slice(), false),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, static_call, optimizer);
let delegate_call = Self::declare(
module,
Self::FUNCTION_DELEGATECALL,
external_call_result_type.fn_type(external_call_arguments.as_slice(), false),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, delegate_call, optimizer);
let mimic_call = Self::declare(
module,
Self::FUNCTION_MIMICCALL,
external_call_result_type.fn_type(mimic_call_arguments.as_slice(), false),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, mimic_call, optimizer);
let far_call_byref = Self::declare(
module,
Self::FUNCTION_FARCALL_BYREF,
external_call_result_type.fn_type(external_call_arguments_by_ref.as_slice(), false),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, far_call_byref, optimizer);
let static_call_byref = Self::declare(
module,
Self::FUNCTION_STATICCALL_BYREF,
external_call_result_type.fn_type(external_call_arguments_by_ref.as_slice(), false),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, static_call_byref, optimizer);
let delegate_call_byref = Self::declare(
module,
Self::FUNCTION_DELEGATECALL_BYREF,
external_call_result_type.fn_type(external_call_arguments_by_ref.as_slice(), false),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, delegate_call_byref, optimizer);
let mimic_call_byref = Self::declare(
module,
Self::FUNCTION_MIMICCALL_BYREF,
external_call_result_type.fn_type(mimic_call_arguments_by_ref.as_slice(), false),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, mimic_call_byref, optimizer);
let r#return = Self::declare(
module,
Self::FUNCTION_RETURN,
llvm.void_type().fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into();
3
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, r#return, optimizer);
let revert = Self::declare(
module,
Self::FUNCTION_REVERT,
llvm.void_type().fn_type(
vec![
llvm.custom_width_int_type(era_compiler_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into();
3
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, revert, optimizer);
Self {
personality,
cxa_throw,
div,
sdiv,
r#mod,
smod,
shl,
shr,
sar,
byte,
add_mod,
mul_mod,
exp,
sign_extend,
mstore8,
sha3,
system_request,
//far_call,
static_call,
delegate_call,
mimic_call,
far_call_byref,
static_call_byref,
delegate_call_byref,
mimic_call_byref,
r#return,
revert,
}
}
///
/// Declares an LLVM runtime function in the `module`,
///
pub fn declare(
module: &inkwell::module::Module<'ctx>,
name: &str,
r#type: inkwell::types::FunctionType<'ctx>,
linkage: Option<inkwell::module::Linkage>,
) -> FunctionDeclaration<'ctx> {
let value = module.add_function(name, r#type, linkage);
FunctionDeclaration::new(r#type, value)
}
///
/// Modifies the external call function with `is_byref` and `is_system` modifiers.
///
pub fn modify(
&self,
function: FunctionDeclaration<'ctx>,
is_byref: bool,
) -> anyhow::Result<FunctionDeclaration<'ctx>> {
let modified = if
/*function == self.far_call {
match is_byref {
false => self.far_call,
true => self.far_call_byref,
}
} else if */
function == self.static_call {
match is_byref {
false => self.static_call,
true => self.static_call_byref,
}
} else if function == self.delegate_call {
match is_byref {
false => self.delegate_call,
true => self.delegate_call_byref,
}
} else if function == self.mimic_call {
match is_byref {
false => self.mimic_call,
true => self.mimic_call_byref,
}
} else {
anyhow::bail!(
"Cannot modify an external call function `{}`",
function.value.get_name().to_string_lossy()
);
};
Ok(modified)
}
}
@@ -0,0 +1,428 @@
//!
//! The LLVM IR generator function.
//!
pub mod block;
pub mod declaration;
pub mod evmla_data;
pub mod intrinsics;
pub mod llvm_runtime;
pub mod r#return;
pub mod runtime;
pub mod vyper_data;
pub mod yul_data;
use std::collections::HashMap;
use crate::eravm::context::attribute::Attribute;
use crate::eravm::context::pointer::Pointer;
use crate::optimizer::settings::size_level::SizeLevel;
use crate::optimizer::Optimizer;
use self::declaration::Declaration;
use self::evmla_data::EVMLAData;
use self::r#return::Return;
use self::runtime::Runtime;
use self::vyper_data::VyperData;
use self::yul_data::YulData;
///
/// The LLVM IR generator function.
///
#[derive(Debug)]
pub struct Function<'ctx> {
/// The high-level source code name.
name: String,
/// The LLVM function declaration.
declaration: Declaration<'ctx>,
/// The stack representation.
stack: HashMap<String, Pointer<'ctx>>,
/// The return value entity.
r#return: Return<'ctx>,
/// The entry block. Each LLVM IR functions must have an entry block.
entry_block: inkwell::basic_block::BasicBlock<'ctx>,
/// The return/leave block. LLVM IR functions may have multiple returning blocks, but it is
/// more reasonable to have a single returning block and other high-level language returns
/// jumping to it. This way it is easier to implement some additional checks and clean-ups
/// before the returning.
return_block: inkwell::basic_block::BasicBlock<'ctx>,
/// The Yul compiler data.
yul_data: Option<YulData>,
/// The EVM legacy assembly compiler data.
evmla_data: Option<EVMLAData<'ctx>>,
/// The Vyper data.
vyper_data: Option<VyperData>,
}
impl<'ctx> Function<'ctx> {
/// The near call ABI function prefix.
pub const ZKSYNC_NEAR_CALL_ABI_PREFIX: &'static str = "ZKSYNC_NEAR_CALL";
/// The near call ABI exception handler name.
pub const ZKSYNC_NEAR_CALL_ABI_EXCEPTION_HANDLER: &'static str = "ZKSYNC_CATCH_NEAR_CALL";
/// The stack hashmap default capacity.
const STACK_HASHMAP_INITIAL_CAPACITY: usize = 64;
///
/// A shortcut constructor.
///
pub fn new(
name: String,
declaration: Declaration<'ctx>,
r#return: Return<'ctx>,
entry_block: inkwell::basic_block::BasicBlock<'ctx>,
return_block: inkwell::basic_block::BasicBlock<'ctx>,
) -> Self {
Self {
name,
declaration,
stack: HashMap::with_capacity(Self::STACK_HASHMAP_INITIAL_CAPACITY),
r#return,
entry_block,
return_block,
yul_data: None,
evmla_data: None,
vyper_data: None,
}
}
///
/// Returns the function name reference.
///
pub fn name(&self) -> &str {
self.name.as_str()
}
///
/// Checks whether the function is defined outside of the front-end.
///
pub fn is_name_external(name: &str) -> bool {
name.starts_with("llvm.")
|| (name.starts_with("__")
&& name != Runtime::FUNCTION_ENTRY
&& name != Runtime::FUNCTION_DEPLOY_CODE
&& name != Runtime::FUNCTION_RUNTIME_CODE)
}
///
/// Checks whether the function is related to the near call ABI.
///
pub fn is_near_call_abi(name: &str) -> bool {
name.starts_with(Self::ZKSYNC_NEAR_CALL_ABI_PREFIX)
|| name == Self::ZKSYNC_NEAR_CALL_ABI_EXCEPTION_HANDLER
}
///
/// Returns the LLVM function declaration.
///
pub fn declaration(&self) -> Declaration<'ctx> {
self.declaration
}
///
/// Returns the N-th parameter of the function.
///
pub fn get_nth_param(&self, index: usize) -> inkwell::values::BasicValueEnum<'ctx> {
self.declaration()
.value
.get_nth_param(index as u32)
.expect("Always exists")
}
///
/// Sets the memory writer function attributes.
///
pub fn set_attributes(
llvm: &'ctx inkwell::context::Context,
declaration: Declaration<'ctx>,
attributes: Vec<Attribute>,
force: bool,
) {
for attribute_kind in attributes.into_iter() {
match attribute_kind {
attribute_kind @ Attribute::AlwaysInline if force => {
let is_optimize_none_set = declaration
.value
.get_enum_attribute(
inkwell::attributes::AttributeLoc::Function,
Attribute::OptimizeNone as u32,
)
.is_some();
if !is_optimize_none_set {
declaration.value.remove_enum_attribute(
inkwell::attributes::AttributeLoc::Function,
Attribute::NoInline as u32,
);
declaration.value.add_attribute(
inkwell::attributes::AttributeLoc::Function,
llvm.create_enum_attribute(attribute_kind as u32, 0),
);
}
}
attribute_kind @ Attribute::NoInline if force => {
declaration.value.remove_enum_attribute(
inkwell::attributes::AttributeLoc::Function,
Attribute::AlwaysInline as u32,
);
declaration.value.add_attribute(
inkwell::attributes::AttributeLoc::Function,
llvm.create_enum_attribute(attribute_kind as u32, 0),
);
}
attribute_kind => declaration.value.add_attribute(
inkwell::attributes::AttributeLoc::Function,
llvm.create_enum_attribute(attribute_kind as u32, 0),
),
}
}
}
///
/// Sets the default attributes.
///
/// The attributes only affect the LLVM optimizations.
///
pub fn set_default_attributes(
llvm: &'ctx inkwell::context::Context,
declaration: Declaration<'ctx>,
optimizer: &Optimizer,
) {
if optimizer.settings().level_middle_end == inkwell::OptimizationLevel::None {
Self::set_attributes(
llvm,
declaration,
vec![Attribute::OptimizeNone, Attribute::NoInline],
false,
);
} else if optimizer.settings().level_middle_end_size == SizeLevel::Z {
Self::set_attributes(
llvm,
declaration,
vec![Attribute::OptimizeForSize, Attribute::MinSize],
false,
);
}
Self::set_attributes(llvm, declaration, vec![Attribute::NoFree], false);
}
///
/// Sets the front-end runtime attributes.
///
pub fn set_frontend_runtime_attributes(
llvm: &'ctx inkwell::context::Context,
declaration: Declaration<'ctx>,
optimizer: &Optimizer,
) {
if optimizer.settings().level_middle_end_size == SizeLevel::Z {
Self::set_attributes(llvm, declaration, vec![Attribute::NoInline], false);
}
}
///
/// Sets the exception handler attributes.
///
pub fn set_exception_handler_attributes(
llvm: &'ctx inkwell::context::Context,
declaration: Declaration<'ctx>,
) {
Self::set_attributes(llvm, declaration, vec![Attribute::NoInline], false);
}
///
/// Sets the CXA-throw attributes.
///
pub fn set_cxa_throw_attributes(
llvm: &'ctx inkwell::context::Context,
declaration: Declaration<'ctx>,
) {
Self::set_attributes(llvm, declaration, vec![Attribute::NoProfile], false);
}
///
/// Sets the pure function attributes.
///
pub fn set_pure_function_attributes(
llvm: &'ctx inkwell::context::Context,
declaration: Declaration<'ctx>,
) {
Self::set_attributes(
llvm,
declaration,
vec![
Attribute::MustProgress,
Attribute::NoUnwind,
// FIXME: LLVM complains about ReadNone being not valid for fns
// Attribute::ReadNone,
Attribute::WillReturn,
],
false,
);
}
///
/// Saves the pointer to a stack variable, returning the pointer to the shadowed variable,
/// if it exists.
///
pub fn insert_stack_pointer(
&mut self,
name: String,
pointer: Pointer<'ctx>,
) -> Option<Pointer<'ctx>> {
self.stack.insert(name, pointer)
}
///
/// Gets the pointer to a stack variable.
///
pub fn get_stack_pointer(&self, name: &str) -> Option<Pointer<'ctx>> {
self.stack.get(name).copied()
}
///
/// Removes the pointer to a stack variable.
///
pub fn remove_stack_pointer(&mut self, name: &str) {
self.stack.remove(name);
}
///
/// Returns the return entity representation.
///
pub fn r#return(&self) -> Return<'ctx> {
self.r#return
}
///
/// Returns the pointer to the function return value.
///
/// # Panics
/// If the pointer has not been set yet.
///
pub fn return_pointer(&self) -> Option<Pointer<'ctx>> {
self.r#return.return_pointer()
}
///
/// Returns the return data size in bytes, based on the default stack alignment.
///
/// # Panics
/// If the pointer has not been set yet.
///
pub fn return_data_size(&self) -> usize {
self.r#return.return_data_size()
}
///
/// Returns the function entry block.
///
pub fn entry_block(&self) -> inkwell::basic_block::BasicBlock<'ctx> {
self.entry_block
}
///
/// Returns the function return block.
///
pub fn return_block(&self) -> inkwell::basic_block::BasicBlock<'ctx> {
self.return_block
}
///
/// Sets the EVM legacy assembly data.
///
pub fn set_evmla_data(&mut self, data: EVMLAData<'ctx>) {
self.evmla_data = Some(data);
}
///
/// Returns the EVM legacy assembly data reference.
///
/// # Panics
/// If the EVM data has not been initialized.
///
pub fn evmla(&self) -> &EVMLAData<'ctx> {
self.evmla_data
.as_ref()
.expect("The EVM data must have been initialized")
}
///
/// Returns the EVM legacy assembly data mutable reference.
///
/// # Panics
/// If the EVM data has not been initialized.
///
pub fn evmla_mut(&mut self) -> &mut EVMLAData<'ctx> {
self.evmla_data
.as_mut()
.expect("The EVM data must have been initialized")
}
///
/// Sets the Vyper data.
///
pub fn set_vyper_data(&mut self, data: VyperData) {
self.vyper_data = Some(data);
}
///
/// Returns the Vyper data reference.
///
/// # Panics
/// If the Vyper data has not been initialized.
///
pub fn vyper(&self) -> &VyperData {
self.vyper_data
.as_ref()
.expect("The Vyper data must have been initialized")
}
///
/// Returns the Vyper data mutable reference.
///
/// # Panics
/// If the Vyper data has not been initialized.
///
pub fn vyper_mut(&mut self) -> &mut VyperData {
self.vyper_data
.as_mut()
.expect("The Vyper data must have been initialized")
}
///
/// Sets the Yul data.
///
pub fn set_yul_data(&mut self, data: YulData) {
self.yul_data = Some(data);
}
///
/// Returns the Yul data reference.
///
/// # Panics
/// If the Yul data has not been initialized.
///
pub fn yul(&self) -> &YulData {
self.yul_data
.as_ref()
.expect("The Yul data must have been initialized")
}
///
/// Returns the Yul data mutable reference.
///
/// # Panics
/// If the Yul data has not been initialized.
///
pub fn yul_mut(&mut self) -> &mut YulData {
self.yul_data
.as_mut()
.expect("The Yul data must have been initialized")
}
}
@@ -0,0 +1,73 @@
//!
//! The LLVM IR generator function return entity.
//!
use crate::eravm::context::pointer::Pointer;
///
/// The LLVM IR generator function return entity.
///
#[derive(Debug, Clone, Copy)]
pub enum Return<'ctx> {
/// The function does not return a value.
None,
/// The function returns a primitive value.
Primitive {
/// The primitive value pointer allocated at the function entry.
pointer: Pointer<'ctx>,
},
/// The function returns a compound value.
/// In this case, the return pointer is allocated on the stack by the callee.
Compound {
/// The structure pointer allocated at the function entry.
pointer: Pointer<'ctx>,
/// The function return type size.
size: usize,
},
}
impl<'ctx> Return<'ctx> {
///
/// A shortcut constructor.
///
pub fn none() -> Self {
Self::None
}
///
/// A shortcut constructor.
///
pub fn primitive(pointer: Pointer<'ctx>) -> Self {
Self::Primitive { pointer }
}
///
/// A shortcut constructor.
///
pub fn compound(pointer: Pointer<'ctx>, size: usize) -> Self {
Self::Compound { pointer, size }
}
///
/// Returns the pointer to the function return value.
///
pub fn return_pointer(&self) -> Option<Pointer<'ctx>> {
match self {
Return::None => None,
Return::Primitive { pointer } => Some(pointer.to_owned()),
Return::Compound { pointer, .. } => Some(pointer.to_owned()),
}
}
///
/// Returns the return data size in bytes, based on the default stack alignment.
///
pub fn return_data_size(&self) -> usize {
era_compiler_common::BYTE_LENGTH_FIELD
* match self {
Self::None => 0,
Self::Primitive { .. } => 1,
Self::Compound { size, .. } => *size,
}
}
}
@@ -0,0 +1,257 @@
//!
//! The `default_call` function.
//!
use inkwell::types::BasicType;
use inkwell::values::BasicValue;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::function::declaration::Declaration as FunctionDeclaration;
use crate::eravm::context::function::llvm_runtime::LLVMRuntime;
use crate::eravm::context::function::Function;
use crate::eravm::context::pointer::Pointer;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
use crate::eravm::WriteLLVM;
///
/// The `default_call` function.
///
/// Generates a default contract call, if the `msg.value` is zero.
///
#[derive(Debug)]
pub struct DefaultCall {
/// The name of the inner function used for the low-level call.
inner_name: String,
/// The function name with the low-level function name as an element.
name: String,
}
impl DefaultCall {
/// The gas argument index.
pub const ARGUMENT_INDEX_GAS: usize = 0;
/// The address argument index.
pub const ARGUMENT_INDEX_ADDRESS: usize = 1;
/// The input offset argument index.
pub const ARGUMENT_INDEX_INPUT_OFFSET: usize = 2;
/// The input length argument index.
pub const ARGUMENT_INDEX_INPUT_LENGTH: usize = 3;
/// The output offset argument index.
pub const ARGUMENT_INDEX_OUTPUT_OFFSET: usize = 4;
/// The output length argument index.
pub const ARGUMENT_INDEX_OUTPUT_LENGTH: usize = 5;
///
/// A shortcut constructor.
///
pub fn new(call_function: FunctionDeclaration) -> Self {
let inner_name = call_function.value.get_name().to_string_lossy().to_string();
let name = Self::name(call_function);
Self { inner_name, name }
}
///
/// Returns the function name.
///
pub fn name(call_function: FunctionDeclaration) -> String {
let suffix = match call_function.value.get_name().to_string_lossy() {
name if name == LLVMRuntime::FUNCTION_FARCALL => "far",
name if name == LLVMRuntime::FUNCTION_STATICCALL => "static",
name if name == LLVMRuntime::FUNCTION_DELEGATECALL => "delegate",
name => panic!("Invalid low-level call inner function `{name}`"),
};
format!("__default_{suffix}_call")
}
///
/// Returns the low-level call function.
///
fn inner_function<'ctx, D>(&self, context: &Context<'ctx, D>) -> FunctionDeclaration<'ctx>
where
D: Dependency + Clone,
{
match self.inner_name.as_str() {
//name if name == LLVMRuntime::FUNCTION_FARCALL => context.llvm_runtime().far_call,
name if name == LLVMRuntime::FUNCTION_STATICCALL => context.llvm_runtime().static_call,
name if name == LLVMRuntime::FUNCTION_DELEGATECALL => {
context.llvm_runtime().delegate_call
}
name => panic!("Invalid low-level call inner function `{name}`"),
}
}
}
impl<D> WriteLLVM<D> for DefaultCall
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
let function_type = context.function_type(
vec![
context.field_type().as_basic_type_enum(),
context.field_type().as_basic_type_enum(),
context.field_type().as_basic_type_enum(),
context.field_type().as_basic_type_enum(),
context.field_type().as_basic_type_enum(),
context.field_type().as_basic_type_enum(),
],
1,
false,
);
let function = context.add_function(
self.name.as_str(),
function_type,
1,
Some(inkwell::module::Linkage::Private),
)?;
Function::set_frontend_runtime_attributes(
context.llvm,
function.borrow().declaration(),
&context.optimizer,
);
Ok(())
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
context.set_current_function(self.name.as_str())?;
/*
let gas = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_GAS)
.into_int_value();
let address = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_ADDRESS)
.into_int_value();
let input_offset = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_INPUT_OFFSET)
.into_int_value();
let input_length = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_INPUT_LENGTH)
.into_int_value();
let output_offset = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_OUTPUT_OFFSET)
.into_int_value();
let output_length = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_OUTPUT_LENGTH)
.into_int_value();
*/
context.set_basic_block(context.current_function().borrow().entry_block());
let status_code_result_pointer = context.build_alloca(
context.field_type(),
"contract_call_result_status_code_pointer",
);
/*
context.build_store(status_code_result_pointer, context.field_const(0));
let abi_data = crate::eravm::utils::abi_data(
context,
input_offset,
input_length,
Some(gas),
AddressSpace::Heap,
false,
)?
.into_int_value();
let result = context
.build_call(
self.inner_function(context),
crate::eravm::utils::external_call_arguments(
context,
abi_data.as_basic_value_enum(),
address,
vec![],
None,
)
.as_slice(),
"contract_call_external",
)
.expect("IntrinsicFunction always returns a flag");
let result_abi_data = context
.builder()
.build_extract_value(
result.into_struct_value(),
0,
"contract_call_external_result_abi_data",
)
.expect("Always exists");
let result_abi_data_pointer = Pointer::new(
context.byte_type(),
AddressSpace::Generic,
result_abi_data.into_pointer_value(),
);
let result_abi_data_casted = result_abi_data_pointer.cast(context.field_type());
let result_status_code_boolean = context
.builder()
.build_extract_value(
result.into_struct_value(),
1,
"contract_call_external_result_status_code_boolean",
)
.expect("Always exists");
let result_status_code = context.builder().build_int_z_extend_or_bit_cast(
result_status_code_boolean.into_int_value(),
context.field_type(),
"contract_call_external_result_status_code",
)?;
context.build_store(status_code_result_pointer, result_status_code);
let source = result_abi_data_casted;
let destination = Pointer::new_with_offset(
context,
AddressSpace::Heap,
context.byte_type(),
output_offset,
"contract_call_destination",
);
context.build_memcpy_return_data(
context.intrinsics().memory_copy_from_generic,
destination,
source,
output_length,
"contract_call_memcpy_from_child",
);
context.write_abi_pointer(
result_abi_data_pointer,
crate::eravm::GLOBAL_RETURN_DATA_POINTER,
);
context.write_abi_data_size(
result_abi_data_pointer,
crate::eravm::GLOBAL_RETURN_DATA_SIZE,
);
*/
context.build_unconditional_branch(context.current_function().borrow().return_block());
context.set_basic_block(context.current_function().borrow().return_block());
let status_code_result =
context.build_load(status_code_result_pointer, "contract_call_status_code")?;
context.build_return(Some(&status_code_result));
Ok(())
}
}
@@ -0,0 +1,105 @@
//!
//! The deploy code function.
//!
use std::marker::PhantomData;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::code_type::CodeType;
use crate::eravm::context::function::runtime::Runtime;
use crate::eravm::context::pointer::Pointer;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
use crate::eravm::WriteLLVM;
///
/// The deploy code function.
///
/// Is a special function that is only used by the front-end generated code.
///
#[derive(Debug)]
pub struct DeployCode<B, D>
where
B: WriteLLVM<D>,
D: Dependency + Clone,
{
/// The deploy code AST representation.
inner: B,
/// The `D` phantom data.
_pd: PhantomData<D>,
}
impl<B, D> DeployCode<B, D>
where
B: WriteLLVM<D>,
D: Dependency + Clone,
{
///
/// A shortcut constructor.
///
pub fn new(inner: B) -> Self {
Self {
inner,
_pd: PhantomData,
}
}
}
impl<B, D> WriteLLVM<D> for DeployCode<B, D>
where
B: WriteLLVM<D>,
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
let function_type =
context.function_type::<inkwell::types::BasicTypeEnum>(vec![], 0, false);
context.add_function(
Runtime::FUNCTION_DEPLOY_CODE,
function_type,
0,
Some(inkwell::module::Linkage::External),
)?;
self.inner.declare(context)
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
context.set_current_function(Runtime::FUNCTION_DEPLOY_CODE)?;
context.set_basic_block(context.current_function().borrow().entry_block());
context.set_code_type(CodeType::Deploy);
if let Some(vyper) = context.vyper_data.as_ref() {
for index in 0..vyper.immutables_size() / era_compiler_common::BYTE_LENGTH_FIELD {
let offset = (crate::eravm::r#const::HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA
as usize)
+ (1 + index) * 2 * era_compiler_common::BYTE_LENGTH_FIELD;
let value = index * era_compiler_common::BYTE_LENGTH_FIELD;
let pointer = Pointer::new_with_offset(
context,
AddressSpace::HeapAuxiliary,
context.field_type(),
context.field_const(offset as u64),
"immutable_index_initializer",
);
context.build_store(pointer, context.field_const(value as u64))?;
}
}
self.inner.into_llvm(context)?;
match context
.basic_block()
.get_last_instruction()
.map(|instruction| instruction.get_opcode())
{
Some(inkwell::values::InstructionOpcode::Br) => {}
Some(inkwell::values::InstructionOpcode::Switch) => {}
_ => context
.build_unconditional_branch(context.current_function().borrow().return_block()),
}
context.set_basic_block(context.current_function().borrow().return_block());
context.build_return(None);
Ok(())
}
}
@@ -0,0 +1,333 @@
//!
//! The `deployer_call` function.
//!
use inkwell::types::BasicType;
use inkwell::values::BasicValue;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::function::Function;
use crate::eravm::context::pointer::Pointer;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
use crate::eravm::WriteLLVM;
///
/// The `deployer_call` function.
///
/// Calls the deployer system contract, which returns the newly deployed contract address or 0.
///
/// The address is returned in the first 32-byte word of the return data. If it is 0, the 0 is
/// returned. If the entire call has failed, there is also a 0 returned.
///
#[derive(Debug)]
pub struct DeployerCall {
/// The address space where the calldata is allocated.
/// Solidity uses the ordinary heap. Vyper uses the auxiliary heap.
address_space: AddressSpace,
}
impl DeployerCall {
/// The default function name.
pub const FUNCTION_NAME: &'static str = "__deployer_call";
/// The value argument index.
pub const ARGUMENT_INDEX_VALUE: usize = 0;
/// The input offset argument index.
pub const ARGUMENT_INDEX_INPUT_OFFSET: usize = 1;
/// The input length argument index.
pub const ARGUMENT_INDEX_INPUT_LENGTH: usize = 2;
/// The signature hash argument index.
pub const ARGUMENT_INDEX_SIGNATURE_HASH: usize = 3;
/// The salt argument index.
pub const ARGUMENT_INDEX_SALT: usize = 4;
///
/// A shortcut constructor.
///
pub fn new(address_space: AddressSpace) -> Self {
Self { address_space }
}
}
impl<D> WriteLLVM<D> for DeployerCall
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
let function_type = context.function_type(
vec![
context.field_type().as_basic_type_enum(),
context.field_type().as_basic_type_enum(),
context.field_type().as_basic_type_enum(),
context.field_type().as_basic_type_enum(),
context.field_type().as_basic_type_enum(),
],
1,
false,
);
let function = context.add_function(
Self::FUNCTION_NAME,
function_type,
1,
Some(inkwell::module::Linkage::External),
)?;
Function::set_frontend_runtime_attributes(
context.llvm,
function.borrow().declaration(),
&context.optimizer,
);
Ok(())
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
context.set_current_function(Self::FUNCTION_NAME)?;
let value = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_VALUE)
.into_int_value();
let input_offset = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_INPUT_OFFSET)
.into_int_value();
let input_length = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_INPUT_LENGTH)
.into_int_value();
let signature_hash = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_SIGNATURE_HASH)
.into_int_value();
let salt = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_SALT)
.into_int_value();
let error_block = context.append_basic_block("deployer_call_error_block");
let success_block = context.append_basic_block("deployer_call_success_block");
let value_zero_block = context.append_basic_block("deployer_call_value_zero_block");
let value_non_zero_block = context.append_basic_block("deployer_call_value_non_zero_block");
let value_join_block = context.append_basic_block("deployer_call_value_join_block");
context.set_basic_block(context.current_function().borrow().entry_block());
let abi_data = crate::eravm::utils::abi_data(
context,
input_offset,
input_length,
None,
self.address_space,
true,
)?;
let signature_pointer = Pointer::new_with_offset(
context,
self.address_space,
context.field_type(),
input_offset,
"deployer_call_signature_pointer",
);
context.build_store(signature_pointer, signature_hash)?;
let salt_offset = context.builder().build_int_add(
input_offset,
context.field_const(era_compiler_common::BYTE_LENGTH_X32 as u64),
"deployer_call_salt_offset",
)?;
let salt_pointer = Pointer::new_with_offset(
context,
self.address_space,
context.field_type(),
salt_offset,
"deployer_call_salt_pointer",
);
context.build_store(salt_pointer, salt)?;
let arguments_offset_offset = context.builder().build_int_add(
salt_offset,
context.field_const((era_compiler_common::BYTE_LENGTH_FIELD * 2) as u64),
"deployer_call_arguments_offset_offset",
)?;
let arguments_offset_pointer = Pointer::new_with_offset(
context,
self.address_space,
context.field_type(),
arguments_offset_offset,
"deployer_call_arguments_offset_pointer",
);
context.build_store(
arguments_offset_pointer,
context.field_const(
(crate::eravm::DEPLOYER_CALL_HEADER_SIZE
- (era_compiler_common::BYTE_LENGTH_X32
+ era_compiler_common::BYTE_LENGTH_FIELD)) as u64,
),
)?;
let arguments_length_offset = context.builder().build_int_add(
arguments_offset_offset,
context.field_const(era_compiler_common::BYTE_LENGTH_FIELD as u64),
"deployer_call_arguments_length_offset",
)?;
let arguments_length_pointer = Pointer::new_with_offset(
context,
self.address_space,
context.field_type(),
arguments_length_offset,
"deployer_call_arguments_length_pointer",
);
let arguments_length_value = context.builder().build_int_sub(
input_length,
context.field_const(crate::eravm::DEPLOYER_CALL_HEADER_SIZE as u64),
"deployer_call_arguments_length",
)?;
context.build_store(arguments_length_pointer, arguments_length_value)?;
let result_pointer =
context.build_alloca(context.field_type(), "deployer_call_result_pointer");
context.build_store(result_pointer, context.field_const(0))?;
let deployer_call_result_type = context.structure_type(&[
context
.byte_type()
.ptr_type(AddressSpace::Generic.into())
.as_basic_type_enum(),
context.bool_type().as_basic_type_enum(),
]);
let deployer_call_result_pointer =
context.build_alloca(deployer_call_result_type, "deployer_call_result_pointer");
context.build_store(
deployer_call_result_pointer,
deployer_call_result_type.const_zero(),
)?;
let is_value_zero = context.builder().build_int_compare(
inkwell::IntPredicate::EQ,
value,
context.field_const(0),
"deployer_call_is_value_zero",
)?;
context.build_conditional_branch(is_value_zero, value_zero_block, value_non_zero_block)?;
context.set_basic_block(value_zero_block);
//let deployer_call_result = context
// .build_call(
// context.llvm_runtime().far_call,
// crate::eravm::utils::external_call_arguments(
// context,
// abi_data,
// context.field_const(zkevm_opcode_defs::ADDRESS_CONTRACT_DEPLOYER.into()),
// vec![],
// None,
// )
// .as_slice(),
// "deployer_call_ordinary",
// )
// .expect("Always returns a value");
//context.build_store(deployer_call_result_pointer, deployer_call_result)?;
context.build_unconditional_branch(value_join_block);
context.set_basic_block(value_non_zero_block);
//let deployer_call_result = context
// .build_call(
// context.llvm_runtime().far_call,
// crate::eravm::utils::external_call_arguments(
// context,
// abi_data.as_basic_value_enum(),
// context.field_const(zkevm_opcode_defs::ADDRESS_MSG_VALUE.into()),
// vec![
// value,
// context.field_const(zkevm_opcode_defs::ADDRESS_CONTRACT_DEPLOYER.into()),
// context.field_const(u64::from(crate::eravm::r#const::SYSTEM_CALL_BIT)),
// ],
// None,
// )
// .as_slice(),
// "deployer_call_system",
// )
// .expect("Always returns a value");
//context.build_store(deployer_call_result_pointer, deployer_call_result)?;
context.build_unconditional_branch(value_join_block);
context.set_basic_block(value_join_block);
let result_abi_data_pointer = context.build_gep(
deployer_call_result_pointer,
&[
context.field_const(0),
context
.integer_type(era_compiler_common::BIT_LENGTH_X32)
.const_zero(),
],
context
.byte_type()
.ptr_type(AddressSpace::Generic.into())
.as_basic_type_enum(),
"deployer_call_result_abi_data_pointer",
);
let result_abi_data =
context.build_load(result_abi_data_pointer, "deployer_call_result_abi_data")?;
let result_status_code_pointer = context.build_gep(
deployer_call_result_pointer,
&[
context.field_const(0),
context
.integer_type(era_compiler_common::BIT_LENGTH_X32)
.const_int(1, false),
],
context.bool_type().as_basic_type_enum(),
"contract_call_external_result_status_code_pointer",
);
let result_status_code_boolean = context
.build_load(
result_status_code_pointer,
"contract_call_external_result_status_code_boolean",
)?
.into_int_value();
context.build_conditional_branch(result_status_code_boolean, success_block, error_block)?;
context.set_basic_block(success_block);
let result_abi_data_pointer = Pointer::new(
context.field_type(),
AddressSpace::Generic,
result_abi_data.into_pointer_value(),
);
let address_or_status_code = context.build_load(
result_abi_data_pointer,
"deployer_call_address_or_status_code",
)?;
context.build_store(result_pointer, address_or_status_code)?;
context.build_unconditional_branch(context.current_function().borrow().return_block());
context.set_basic_block(error_block);
let result_abi_data_pointer = Pointer::new(
context.byte_type(),
AddressSpace::Generic,
result_abi_data.into_pointer_value(),
);
context.write_abi_pointer(
result_abi_data_pointer,
crate::eravm::GLOBAL_RETURN_DATA_POINTER,
);
context.write_abi_data_size(
result_abi_data_pointer,
crate::eravm::GLOBAL_RETURN_DATA_SIZE,
);
context.build_unconditional_branch(context.current_function().borrow().return_block());
context.set_basic_block(context.current_function().borrow().return_block());
let result = context.build_load(result_pointer, "deployer_call_result")?;
context.build_return(Some(&result));
Ok(())
}
}
@@ -0,0 +1,269 @@
//!
//! The entry function.
//!
use inkwell::types::BasicType;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::function::runtime::Runtime;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
use crate::eravm::WriteLLVM;
use crate::EraVMPointer as Pointer;
///
/// The entry function.
///
/// The function is a wrapper managing the runtime and deploy code calling logic.
///
/// Is a special runtime function that is only used by the front-end generated code.
///
#[derive(Debug, Default)]
pub struct Entry {}
impl Entry {
/// The call flags argument index.
pub const ARGUMENT_INDEX_CALL_FLAGS: usize = 0;
/// The number of mandatory arguments.
pub const MANDATORY_ARGUMENTS_COUNT: usize = 2;
/// Initializes the global variables.
/// The pointers are not initialized, because it's not possible to create a null pointer.
pub fn initialize_globals<D>(context: &mut Context<D>)
where
D: Dependency + Clone,
{
let calldata_type = context.array_type(context.byte_type(), 1024);
context.set_global(
crate::eravm::GLOBAL_CALLDATA_POINTER,
calldata_type,
AddressSpace::Stack,
calldata_type.get_undef(),
);
let heap_memory_type = context.array_type(context.byte_type(), 1024 * 1024);
context.set_global(
crate::eravm::GLOBAL_HEAP_MEMORY_POINTER,
heap_memory_type,
AddressSpace::Stack,
heap_memory_type.get_undef(),
);
context.set_global(
crate::eravm::GLOBAL_CALLDATA_SIZE,
context.field_type(),
AddressSpace::Stack,
context.field_undef(),
);
context.set_global(
crate::eravm::GLOBAL_RETURN_DATA_SIZE,
context.field_type(),
AddressSpace::Stack,
context.field_const(0),
);
context.set_global(
crate::eravm::GLOBAL_CALL_FLAGS,
context.field_type(),
AddressSpace::Stack,
context.field_const(0),
);
let extra_abi_data_type = context.array_type(
context.field_type().as_basic_type_enum(),
crate::eravm::EXTRA_ABI_DATA_SIZE,
);
context.set_global(
crate::eravm::GLOBAL_EXTRA_ABI_DATA,
extra_abi_data_type,
AddressSpace::Stack,
extra_abi_data_type.const_zero(),
);
}
/// Load the calldata via seal `input` and initialize the calldata end
/// and calldata size globals.
pub fn load_calldata<D>(context: &mut Context<D>) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
let input_pointer = context
.get_global(crate::eravm::GLOBAL_CALLDATA_POINTER)?
.value
.as_pointer_value();
let input_pointer_casted = context.builder.build_ptr_to_int(
input_pointer,
context.integer_type(32),
"input_pointer_casted",
)?;
let length_pointer = context.build_alloca(context.integer_type(32), "len_ptr");
let length_pointer_casted = context.builder.build_ptr_to_int(
length_pointer.value,
context.integer_type(32),
"length_pointer_casted",
)?;
context.build_store(length_pointer, context.integer_const(32, 1024))?;
context.builder().build_call(
context.module().get_function("input").expect("is declared"),
&[input_pointer_casted.into(), length_pointer_casted.into()],
"call_seal_input",
)?;
// Store the calldata size
let calldata_size = context
.build_load(length_pointer, "input_size")?
.into_int_value();
let calldata_size_casted = context.builder().build_int_z_extend(
calldata_size,
context.field_type(),
"zext_input_len",
)?;
context.set_global(
crate::eravm::GLOBAL_CALLDATA_SIZE,
context.field_type(),
AddressSpace::Stack,
calldata_size_casted,
);
// Store calldata end pointer
let input_pointer = Pointer::new(
input_pointer.get_type(),
AddressSpace::Generic,
input_pointer,
);
let calldata_end_pointer = context.build_gep(
input_pointer,
&[calldata_size_casted],
context
.byte_type()
.ptr_type(AddressSpace::Generic.into())
.as_basic_type_enum(),
"return_data_abi_initializer",
);
context.write_abi_pointer(
calldata_end_pointer,
crate::eravm::GLOBAL_RETURN_DATA_POINTER,
);
context.write_abi_pointer(calldata_end_pointer, crate::eravm::GLOBAL_ACTIVE_POINTER);
Ok(())
}
/// Calls the deploy code if the first function argument was `1`.
/// Calls the runtime code otherwise.
pub fn leave_entry<D>(context: &mut Context<D>) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
let is_deploy = context
.current_function()
.borrow()
.get_nth_param(Self::ARGUMENT_INDEX_CALL_FLAGS);
context.set_global(
crate::eravm::GLOBAL_CALL_FLAGS,
is_deploy.get_type(),
AddressSpace::Stack,
is_deploy.into_int_value(),
);
let deploy_code_call_block = context.append_basic_block("deploy_code_call_block");
let runtime_code_call_block = context.append_basic_block("runtime_code_call_block");
context.build_conditional_branch(
is_deploy.into_int_value(),
deploy_code_call_block,
runtime_code_call_block,
)?;
let deploy_code = context
.functions
.get(Runtime::FUNCTION_DEPLOY_CODE)
.cloned()
.ok_or_else(|| anyhow::anyhow!("Contract deploy code not found"))?;
let runtime_code = context
.functions
.get(Runtime::FUNCTION_RUNTIME_CODE)
.cloned()
.ok_or_else(|| anyhow::anyhow!("Contract runtime code not found"))?;
context.set_basic_block(deploy_code_call_block);
context.build_invoke(deploy_code.borrow().declaration, &[], "deploy_code_call");
context.build_unconditional_branch(context.current_function().borrow().return_block());
context.set_basic_block(runtime_code_call_block);
context.build_invoke(runtime_code.borrow().declaration, &[], "runtime_code_call");
context.build_unconditional_branch(context.current_function().borrow().return_block());
Ok(())
}
}
impl<D> WriteLLVM<D> for Entry
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
let entry_arguments = vec![context.bool_type().as_basic_type_enum()];
let entry_function_type = context.function_type(entry_arguments, 0, false);
context.add_function(Runtime::FUNCTION_ENTRY, entry_function_type, 0, None)?;
context.declare_extern_function("deploy")?;
context.declare_extern_function("call")?;
Ok(())
}
/// Instead of a single entrypoint, the runtime expects two exports: `call ` and `deploy`.
/// `call` and `deploy` directly call `entry`, signaling a deploy if the first arg is `1`.
/// The `entry` function loads calldata, sets globals and calls the runtime or deploy code.
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
let entry = context
.get_function(Runtime::FUNCTION_ENTRY)
.expect("the entry function should already be declared")
.borrow()
.declaration;
crate::EraVMFunction::set_attributes(
context.llvm(),
entry,
vec![crate::EraVMAttribute::NoReturn],
true,
);
context.set_current_function("deploy")?;
context.set_basic_block(context.current_function().borrow().entry_block());
assert!(context
.build_call(entry, &[context.bool_const(true).into()], "entry_deploy")
.is_none());
context.set_basic_block(context.current_function().borrow().return_block);
context.build_unreachable();
context.set_current_function("call")?;
context.set_basic_block(context.current_function().borrow().entry_block());
assert!(context
.build_call(entry, &[context.bool_const(false).into()], "entry_call")
.is_none());
context.set_basic_block(context.current_function().borrow().return_block);
context.build_unreachable();
context.set_current_function(Runtime::FUNCTION_ENTRY)?;
context.set_basic_block(context.current_function().borrow().entry_block());
Self::initialize_globals(context);
Self::load_calldata(context)?;
Self::leave_entry(context)?;
context.build_unconditional_branch(context.current_function().borrow().return_block());
context.set_basic_block(context.current_function().borrow().return_block());
context.build_unreachable();
Ok(())
}
}
@@ -0,0 +1,100 @@
//!
//! The front-end runtime functions.
//!
pub mod default_call;
pub mod deploy_code;
pub mod deployer_call;
pub mod entry;
pub mod runtime_code;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::function::declaration::Declaration as FunctionDeclaration;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
use crate::eravm::WriteLLVM;
use self::default_call::DefaultCall;
use self::deployer_call::DeployerCall;
///
/// The front-end runtime functions.
///
#[derive(Debug, Clone)]
pub struct Runtime {
/// The address space where the calldata is allocated.
/// Solidity uses the ordinary heap. Vyper uses the auxiliary heap.
address_space: AddressSpace,
}
impl Runtime {
/// The main entry function name.
pub const FUNCTION_ENTRY: &'static str = "__entry";
/// The deploy code function name.
pub const FUNCTION_DEPLOY_CODE: &'static str = "__deploy";
/// The runtime code function name.
pub const FUNCTION_RUNTIME_CODE: &'static str = "__runtime";
///
/// A shortcut constructor.
///
pub fn new(address_space: AddressSpace) -> Self {
Self { address_space }
}
///
/// Returns the corresponding runtime function.
///
pub fn default_call<'ctx, D>(
context: &Context<'ctx, D>,
call_function: FunctionDeclaration<'ctx>,
) -> FunctionDeclaration<'ctx>
where
D: Dependency + Clone,
{
context
.get_function(DefaultCall::name(call_function).as_str())
.expect("Always exists")
.borrow()
.declaration()
}
///
/// Returns the corresponding runtime function.
///
pub fn deployer_call<'ctx, D>(context: &Context<'ctx, D>) -> FunctionDeclaration<'ctx>
where
D: Dependency + Clone,
{
context
.get_function(DeployerCall::FUNCTION_NAME)
.expect("Always exists")
.borrow()
.declaration()
}
}
impl<D> WriteLLVM<D> for Runtime
where
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
//DefaultCall::new(context.llvm_runtime().far_call).declare(context)?;
DefaultCall::new(context.llvm_runtime().static_call).declare(context)?;
DefaultCall::new(context.llvm_runtime().delegate_call).declare(context)?;
DeployerCall::new(self.address_space).declare(context)?;
Ok(())
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
//DefaultCall::new(context.llvm_runtime().far_call).into_llvm(context)?;
DefaultCall::new(context.llvm_runtime().static_call).into_llvm(context)?;
DefaultCall::new(context.llvm_runtime().delegate_call).into_llvm(context)?;
DeployerCall::new(self.address_space).into_llvm(context)?;
Ok(())
}
}
@@ -0,0 +1,86 @@
//!
//! The runtime code function.
//!
use std::marker::PhantomData;
use crate::eravm::context::code_type::CodeType;
use crate::eravm::context::function::runtime::Runtime;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
use crate::eravm::WriteLLVM;
///
/// The runtime code function.
///
/// Is a special function that is only used by the front-end generated code.
///
#[derive(Debug)]
pub struct RuntimeCode<B, D>
where
B: WriteLLVM<D>,
D: Dependency + Clone,
{
/// The runtime code AST representation.
inner: B,
/// The `D` phantom data.
_pd: PhantomData<D>,
}
impl<B, D> RuntimeCode<B, D>
where
B: WriteLLVM<D>,
D: Dependency + Clone,
{
///
/// A shortcut constructor.
///
pub fn new(inner: B) -> Self {
Self {
inner,
_pd: PhantomData,
}
}
}
impl<B, D> WriteLLVM<D> for RuntimeCode<B, D>
where
B: WriteLLVM<D>,
D: Dependency + Clone,
{
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
let function_type =
context.function_type::<inkwell::types::BasicTypeEnum>(vec![], 0, false);
context.add_function(
Runtime::FUNCTION_RUNTIME_CODE,
function_type,
0,
Some(inkwell::module::Linkage::External),
)?;
self.inner.declare(context)
}
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
context.set_current_function(Runtime::FUNCTION_RUNTIME_CODE)?;
context.set_basic_block(context.current_function().borrow().entry_block());
context.set_code_type(CodeType::Runtime);
self.inner.into_llvm(context)?;
match context
.basic_block()
.get_last_instruction()
.map(|instruction| instruction.get_opcode())
{
Some(inkwell::values::InstructionOpcode::Br) => {}
Some(inkwell::values::InstructionOpcode::Switch) => {}
_ => context
.build_unconditional_branch(context.current_function().borrow().return_block()),
}
context.set_basic_block(context.current_function().borrow().return_block());
context.build_unreachable();
Ok(())
}
}
@@ -0,0 +1,52 @@
//!
//! The LLVM function Vyper data.
//!
use std::collections::HashMap;
///
/// The LLVM function Vyper data.
///
/// Describes some data that is only relevant to Vyper.
///
#[derive(Debug)]
pub struct VyperData {
/// The block-local variables. They are still allocated at the beginning of the function,
/// but their parent block must be known in order to pass the implicit arguments thereto.
/// Is only used by the Vyper LLL IR compiler.
label_arguments: HashMap<String, Vec<String>>,
}
impl Default for VyperData {
fn default() -> Self {
Self {
label_arguments: HashMap::with_capacity(Self::LABEL_ARGUMENTS_HASHMAP_INITIAL_CAPACITY),
}
}
}
impl VyperData {
/// The label arguments hashmap default capacity.
const LABEL_ARGUMENTS_HASHMAP_INITIAL_CAPACITY: usize = 16;
///
/// A shortcut constructor.
///
pub fn new() -> Self {
Self::default()
}
///
/// Returns the list of a Vyper label arguments.
///
pub fn label_arguments(&self, label_name: &str) -> Option<Vec<String>> {
self.label_arguments.get(label_name).cloned()
}
///
/// Inserts arguments for the specified label.
///
pub fn insert_label_arguments(&mut self, label_name: String, arguments: Vec<String>) {
self.label_arguments.insert(label_name, arguments);
}
}
@@ -0,0 +1,53 @@
//!
//! The LLVM function Yul data.
//!
use std::collections::HashMap;
use num::BigUint;
///
/// The LLVM function Yul data.
///
/// Describes some data that is only relevant to Yul.
///
#[derive(Debug)]
pub struct YulData {
/// The constants saved to variables. Used for peculiar cases like call simulation.
/// It is a partial implementation of the constant propagation.
constants: HashMap<String, BigUint>,
}
impl Default for YulData {
fn default() -> Self {
Self {
constants: HashMap::with_capacity(Self::CONSTANTS_HASHMAP_INITIAL_CAPACITY),
}
}
}
impl YulData {
/// The constants hashmap default capacity.
const CONSTANTS_HASHMAP_INITIAL_CAPACITY: usize = 16;
///
/// A shortcut constructor.
///
pub fn new() -> Self {
Self::default()
}
///
/// Returns a constant if it has been saved.
///
pub fn get_constant(&self, name: &str) -> Option<BigUint> {
self.constants.get(name).cloned()
}
///
/// Saves a constant detected with the partial constant propagation.
///
pub fn insert_constant(&mut self, name: String, value: BigUint) {
self.constants.insert(name, value);
}
}
@@ -0,0 +1,63 @@
//!
//! The LLVM global value.
//!
use inkwell::types::BasicType;
use inkwell::values::BasicValue;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::Context;
use crate::EraVMDependency;
///
/// The LLVM global value.
///
#[derive(Debug, Clone, Copy)]
pub struct Global<'ctx> {
/// The global type.
pub r#type: inkwell::types::BasicTypeEnum<'ctx>,
/// The global value.
pub value: inkwell::values::GlobalValue<'ctx>,
}
impl<'ctx> Global<'ctx> {
///
/// A shortcut constructor.
///
pub fn new<D, T, V>(
context: &mut Context<'ctx, D>,
r#type: T,
address_space: AddressSpace,
initializer: V,
name: &str,
) -> Self
where
D: EraVMDependency + Clone,
T: BasicType<'ctx>,
V: BasicValue<'ctx>,
{
let r#type = r#type.as_basic_type_enum();
let value = context
.module()
.add_global(r#type, Some(address_space.into()), name);
let global = Self { r#type, value };
global.value.set_linkage(inkwell::module::Linkage::Private);
global
.value
.set_visibility(inkwell::GlobalVisibility::Default);
global.value.set_externally_initialized(false);
if let AddressSpace::Code = address_space {
global.value.set_constant(true);
}
if !r#type.is_pointer_type() {
global.value.set_initializer(&initializer);
} else {
global.value.set_initializer(&r#type.const_zero());
context.build_store(global.into(), initializer).unwrap();
}
global
}
}
@@ -0,0 +1,33 @@
//!
//! The LLVM IR generator loop.
//!
///
/// The LLVM IR generator loop.
///
#[derive(Debug, Clone)]
pub struct Loop<'ctx> {
/// The loop current block.
pub body_block: inkwell::basic_block::BasicBlock<'ctx>,
/// The increment block before the body.
pub continue_block: inkwell::basic_block::BasicBlock<'ctx>,
/// The join block after the body.
pub join_block: inkwell::basic_block::BasicBlock<'ctx>,
}
impl<'ctx> Loop<'ctx> {
///
/// A shortcut constructor.
///
pub fn new(
body_block: inkwell::basic_block::BasicBlock<'ctx>,
continue_block: inkwell::basic_block::BasicBlock<'ctx>,
join_block: inkwell::basic_block::BasicBlock<'ctx>,
) -> Self {
Self {
body_block,
continue_block,
join_block,
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,137 @@
//!
//! The LLVM pointer.
//!
use inkwell::types::BasicType;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::global::Global;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// The LLVM pointer.
///
#[derive(Debug, Clone, Copy)]
pub struct Pointer<'ctx> {
/// The pointee type.
pub r#type: inkwell::types::BasicTypeEnum<'ctx>,
/// The address space.
pub address_space: AddressSpace,
/// The pointer value.
pub value: inkwell::values::PointerValue<'ctx>,
}
impl<'ctx> Pointer<'ctx> {
///
/// A shortcut constructor.
///
pub fn new<T>(
r#type: T,
address_space: AddressSpace,
value: inkwell::values::PointerValue<'ctx>,
) -> Self
where
T: BasicType<'ctx>,
{
Self {
r#type: r#type.as_basic_type_enum(),
address_space,
value,
}
}
///
/// Wraps a 256-bit primitive type pointer.
///
pub fn new_stack_field<D>(
context: &Context<'ctx, D>,
value: inkwell::values::PointerValue<'ctx>,
) -> Self
where
D: Dependency + Clone,
{
Self {
r#type: context.field_type().as_basic_type_enum(),
address_space: AddressSpace::Stack,
value,
}
}
///
/// Creates a new pointer with the specified `offset`.
///
pub fn new_with_offset<D, T>(
context: &Context<'ctx, D>,
address_space: AddressSpace,
r#type: T,
offset: inkwell::values::IntValue<'ctx>,
name: &str,
) -> Self
where
D: Dependency + Clone,
T: BasicType<'ctx>,
{
assert_ne!(
address_space,
AddressSpace::Stack,
"Stack pointers cannot be addressed"
);
let value = context
.builder
.build_int_to_ptr(
offset,
context.byte_type().ptr_type(address_space.into()),
name,
)
.unwrap();
Self::new(r#type, address_space, value)
}
///
/// Casts the pointer into another type.
///
pub fn cast<T>(self, r#type: T) -> Self
where
T: BasicType<'ctx>,
{
Self {
r#type: r#type.as_basic_type_enum(),
address_space: self.address_space,
value: self.value,
}
}
pub fn address_space_cast<D>(
self,
context: &Context<'ctx, D>,
address_space: AddressSpace,
name: &str,
) -> anyhow::Result<Self>
where
D: Dependency + Clone,
{
let value = context.builder().build_address_space_cast(
self.value,
self.r#type.ptr_type(address_space.into()),
name,
)?;
Ok(Self {
address_space,
value,
..self
})
}
}
impl<'ctx> From<Global<'ctx>> for Pointer<'ctx> {
fn from(global: Global<'ctx>) -> Self {
Self {
r#type: global.r#type,
address_space: AddressSpace::Stack,
value: global.value.as_pointer_value(),
}
}
}
@@ -0,0 +1,59 @@
//!
//! The LLVM IR generator Solidity data.
//!
use std::collections::BTreeMap;
///
/// The LLVM IR generator Solidity data.
///
/// Describes some data that is only relevant to Solidity.
///
#[derive(Debug, Default)]
pub struct SolidityData {
/// The immutables identifier-to-offset mapping. Is only used by Solidity due to
/// the arbitrariness of its identifiers.
immutables: BTreeMap<String, usize>,
}
impl SolidityData {
///
/// A shortcut constructor.
///
pub fn new() -> Self {
Self::default()
}
///
/// Returns the current number of immutables values in the contract.
///
pub fn immutables_size(&self) -> usize {
self.immutables.len() * era_compiler_common::BYTE_LENGTH_FIELD
}
///
/// Allocates memory for an immutable value in the auxiliary heap.
///
/// If the identifier is already known, just returns its offset.
///
pub fn allocate_immutable(&mut self, identifier: &str) -> usize {
let number_of_elements = self.immutables.len();
let new_offset = number_of_elements * era_compiler_common::BYTE_LENGTH_FIELD;
*self
.immutables
.entry(identifier.to_owned())
.or_insert(new_offset)
}
///
/// Gets the offset of the immutable value.
///
/// If the value is not yet allocated, then it is done forcibly.
///
pub fn get_or_allocate_immutable(&mut self, identifier: &str) -> usize {
match self.immutables.get(identifier).copied() {
Some(offset) => offset,
None => self.allocate_immutable(identifier),
}
}
}
@@ -0,0 +1,136 @@
//!
//! The LLVM IR generator context tests.
//!
use crate::eravm::context::attribute::Attribute;
use crate::eravm::context::Context;
use crate::eravm::DummyDependency;
use crate::optimizer::settings::Settings as OptimizerSettings;
use crate::optimizer::Optimizer;
pub fn create_context(
llvm: &inkwell::context::Context,
optimizer_settings: OptimizerSettings,
) -> Context<DummyDependency> {
crate::eravm::initialize_target();
let module = llvm.create_module("test");
let optimizer = Optimizer::new(optimizer_settings);
Context::<DummyDependency>::new(&llvm, module, optimizer, None, true, None)
}
#[test]
pub fn check_attribute_null_pointer_is_invalid() {
let llvm = inkwell::context::Context::create();
let mut context = create_context(&llvm, OptimizerSettings::cycles());
let function = context
.add_function(
"test",
context
.field_type()
.fn_type(&[context.field_type().into()], false),
1,
Some(inkwell::module::Linkage::External),
)
.expect("Failed to add function");
assert!(function
.borrow()
.declaration()
.value
.attributes(inkwell::attributes::AttributeLoc::Function)
.contains(&llvm.create_enum_attribute(Attribute::NullPointerIsValid as u32, 0)));
}
#[test]
pub fn check_attribute_optimize_for_size_mode_3() {
let llvm = inkwell::context::Context::create();
let mut context = create_context(&llvm, OptimizerSettings::cycles());
let function = context
.add_function(
"test",
context
.field_type()
.fn_type(&[context.field_type().into()], false),
1,
Some(inkwell::module::Linkage::External),
)
.expect("Failed to add function");
assert!(!function
.borrow()
.declaration()
.value
.attributes(inkwell::attributes::AttributeLoc::Function)
.contains(&llvm.create_enum_attribute(Attribute::OptimizeForSize as u32, 0)));
}
#[test]
pub fn check_attribute_optimize_for_size_mode_z() {
let llvm = inkwell::context::Context::create();
let mut context = create_context(&llvm, OptimizerSettings::size());
let function = context
.add_function(
"test",
context
.field_type()
.fn_type(&[context.field_type().into()], false),
1,
Some(inkwell::module::Linkage::External),
)
.expect("Failed to add function");
assert!(function
.borrow()
.declaration()
.value
.attributes(inkwell::attributes::AttributeLoc::Function)
.contains(&llvm.create_enum_attribute(Attribute::OptimizeForSize as u32, 0)));
}
#[test]
pub fn check_attribute_min_size_mode_3() {
let llvm = inkwell::context::Context::create();
let mut context = create_context(&llvm, OptimizerSettings::cycles());
let function = context
.add_function(
"test",
context
.field_type()
.fn_type(&[context.field_type().into()], false),
1,
Some(inkwell::module::Linkage::External),
)
.expect("Failed to add function");
assert!(!function
.borrow()
.declaration()
.value
.attributes(inkwell::attributes::AttributeLoc::Function)
.contains(&llvm.create_enum_attribute(Attribute::MinSize as u32, 0)));
}
#[test]
pub fn check_attribute_min_size_mode_z() {
let llvm = inkwell::context::Context::create();
let mut context = create_context(&llvm, OptimizerSettings::size());
let function = context
.add_function(
"test",
context
.field_type()
.fn_type(&[context.field_type().into()], false),
1,
Some(inkwell::module::Linkage::External),
)
.expect("Failed to add function");
assert!(function
.borrow()
.declaration()
.value
.attributes(inkwell::attributes::AttributeLoc::Function)
.contains(&llvm.create_enum_attribute(Attribute::MinSize as u32, 0)));
}
@@ -0,0 +1,50 @@
//!
//! The LLVM IR generator Vyper data.
//!
///
/// The LLVM IR generator Vyper data.
///
/// Describes some data that is only relevant to Vyper.
///
#[derive(Debug)]
pub struct VyperData {
/// The immutables size tracker. Stores the size in bytes.
/// Does not take into account the size of the indexes.
immutables_size: usize,
/// Whether the contract forwarder has been used.
is_forwarder_used: bool,
}
impl VyperData {
///
/// A shortcut constructor.
///
pub fn new(immutables_size: usize, is_forwarder_used: bool) -> Self {
Self {
immutables_size,
is_forwarder_used,
}
}
///
/// Returns the size of the immutables data of the contract.
///
pub fn immutables_size(&self) -> usize {
self.immutables_size
}
///
/// Sets the forwarder usage flag.
///
pub fn set_is_forwarder_used(&mut self) {
self.is_forwarder_used = true;
}
///
/// Returns the forwarder usage flag.
///
pub fn is_forwarder_used(&self) -> bool {
self.is_forwarder_used
}
}
@@ -0,0 +1,92 @@
//!
//! The LLVM IR generator Yul data.
//!
use std::collections::BTreeMap;
use num::Zero;
///
/// The LLVM IR generator Yul data.
///
/// Describes some data that is only relevant to Yul.
///
#[derive(Debug, Default)]
pub struct YulData {
/// The system mode flag.
/// The call simulations only work if this mode is enabled.
is_system_mode: bool,
/// The list of constant arrays in the code section.
/// It is a temporary storage used until the finalization method is called.
const_arrays: BTreeMap<u8, Vec<num::BigUint>>,
}
impl YulData {
///
/// A shortcut constructor.
///
pub fn new(is_system_mode: bool) -> Self {
Self {
is_system_mode,
const_arrays: BTreeMap::new(),
}
}
///
/// Whether the system mode is enabled.
///
pub fn is_system_mode(&self) -> bool {
self.is_system_mode
}
///
/// Declares a temporary constant array representation.
///
pub fn const_array_declare(&mut self, index: u8, size: u16) -> anyhow::Result<()> {
if self.const_arrays.contains_key(&index) {
anyhow::bail!(
"The constant array with index {} is already declared",
index
);
}
self.const_arrays
.insert(index, vec![num::BigUint::zero(); size as usize]);
Ok(())
}
///
/// Sets a value in the constant array representation.
///
pub fn const_array_set(
&mut self,
index: u8,
offset: u16,
value: num::BigUint,
) -> anyhow::Result<()> {
let array = self.const_arrays.get_mut(&index).ok_or_else(|| {
anyhow::anyhow!("The constant array with index {} is not declared", index)
})?;
if offset >= array.len() as u16 {
anyhow::bail!(
"The constant array with index {} has size {} but the offset is {}",
index,
array.len(),
offset,
);
}
array[offset as usize] = value;
Ok(())
}
///
/// Finalizes the constant array declaration.
///
pub fn const_array_take(&mut self, index: u8) -> anyhow::Result<Vec<num::BigUint>> {
self.const_arrays.remove(&index).ok_or_else(|| {
anyhow::anyhow!("The constant array with index {} is not declared", index)
})
}
}
@@ -0,0 +1,149 @@
//!
//! Translates the arithmetic operations.
//!
use inkwell::values::BasicValue;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Translates the arithmetic addition.
///
pub fn addition<'ctx, D>(
context: &mut Context<'ctx, D>,
operand_1: inkwell::values::IntValue<'ctx>,
operand_2: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context
.builder()
.build_int_add(operand_1, operand_2, "addition_result")?
.as_basic_value_enum())
}
///
/// Translates the arithmetic subtraction.
///
pub fn subtraction<'ctx, D>(
context: &mut Context<'ctx, D>,
operand_1: inkwell::values::IntValue<'ctx>,
operand_2: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context
.builder()
.build_int_sub(operand_1, operand_2, "subtraction_result")?
.as_basic_value_enum())
}
///
/// Translates the arithmetic multiplication.
///
pub fn multiplication<'ctx, D>(
context: &mut Context<'ctx, D>,
operand_1: inkwell::values::IntValue<'ctx>,
operand_2: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context
.builder()
.build_int_mul(operand_1, operand_2, "multiplication_result")?
.as_basic_value_enum())
}
///
/// Translates the arithmetic division.
///
pub fn division<'ctx, D>(
context: &mut Context<'ctx, D>,
operand_1: inkwell::values::IntValue<'ctx>,
operand_2: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context
.builder()
.build_int_unsigned_div(operand_1, operand_2, "udiv")?
.into())
}
///
/// Translates the arithmetic remainder.
///
pub fn remainder<'ctx, D>(
context: &mut Context<'ctx, D>,
operand_1: inkwell::values::IntValue<'ctx>,
operand_2: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context
.build_call(
context.llvm_runtime().r#mod,
&[
operand_1.as_basic_value_enum(),
operand_2.as_basic_value_enum(),
],
"add_mod_call",
)
.expect("Always exists"))
}
///
/// Translates the signed arithmetic division.
///
/// Two differences between the EVM and LLVM IR:
/// 1. In case of division by zero, 0 is returned.
/// 2. In case of overflow, the first argument is returned.
///
pub fn division_signed<'ctx, D>(
context: &mut Context<'ctx, D>,
operand_1: inkwell::values::IntValue<'ctx>,
operand_2: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context
.build_call(
context.llvm_runtime().sdiv,
&[
operand_1.as_basic_value_enum(),
operand_2.as_basic_value_enum(),
],
"add_mod_call",
)
.expect("Always exists"))
}
///
/// Translates the signed arithmetic remainder.
///
pub fn remainder_signed<'ctx, D>(
context: &mut Context<'ctx, D>,
operand_1: inkwell::values::IntValue<'ctx>,
operand_2: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context
.build_call(
context.llvm_runtime().smod,
&[
operand_1.as_basic_value_enum(),
operand_2.as_basic_value_enum(),
],
"add_mod_call",
)
.expect("Always exists"))
}
@@ -0,0 +1,235 @@
//!
//! Translates the bitwise operations.
//!
use inkwell::values::BasicValue;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Translates the bitwise OR.
///
pub fn or<'ctx, D>(
context: &mut Context<'ctx, D>,
operand_1: inkwell::values::IntValue<'ctx>,
operand_2: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context
.builder()
.build_or(operand_1, operand_2, "or_result")?
.as_basic_value_enum())
}
///
/// Translates the bitwise XOR.
///
pub fn xor<'ctx, D>(
context: &mut Context<'ctx, D>,
operand_1: inkwell::values::IntValue<'ctx>,
operand_2: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context
.builder()
.build_xor(operand_1, operand_2, "xor_result")?
.as_basic_value_enum())
}
///
/// Translates the bitwise AND.
///
pub fn and<'ctx, D>(
context: &mut Context<'ctx, D>,
operand_1: inkwell::values::IntValue<'ctx>,
operand_2: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context
.builder()
.build_and(operand_1, operand_2, "and_result")?
.as_basic_value_enum())
}
///
/// Translates the bitwise shift left.
///
pub fn shift_left<'ctx, D>(
context: &mut Context<'ctx, D>,
shift: inkwell::values::IntValue<'ctx>,
value: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let overflow_block = context.append_basic_block("shift_left_overflow");
let non_overflow_block = context.append_basic_block("shift_left_non_overflow");
let join_block = context.append_basic_block("shift_left_join");
let result_pointer = context.build_alloca(context.field_type(), "shift_left_result_pointer");
let condition_is_overflow = context.builder().build_int_compare(
inkwell::IntPredicate::UGT,
shift,
context.field_const((era_compiler_common::BIT_LENGTH_FIELD - 1) as u64),
"shift_left_is_overflow",
)?;
context.build_conditional_branch(condition_is_overflow, overflow_block, non_overflow_block)?;
context.set_basic_block(overflow_block);
context.build_store(result_pointer, context.field_const(0))?;
context.build_unconditional_branch(join_block);
context.set_basic_block(non_overflow_block);
let value =
context
.builder()
.build_left_shift(value, shift, "shift_left_non_overflow_result")?;
context.build_store(result_pointer, value)?;
context.build_unconditional_branch(join_block);
context.set_basic_block(join_block);
context.build_load(result_pointer, "shift_left_result")
}
///
/// Translates the bitwise shift right.
///
pub fn shift_right<'ctx, D>(
context: &mut Context<'ctx, D>,
shift: inkwell::values::IntValue<'ctx>,
value: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let overflow_block = context.append_basic_block("shift_right_overflow");
let non_overflow_block = context.append_basic_block("shift_right_non_overflow");
let join_block = context.append_basic_block("shift_right_join");
let result_pointer = context.build_alloca(context.field_type(), "shift_right_result_pointer");
let condition_is_overflow = context.builder().build_int_compare(
inkwell::IntPredicate::UGT,
shift,
context.field_const((era_compiler_common::BIT_LENGTH_FIELD - 1) as u64),
"shift_right_is_overflow",
)?;
context.build_conditional_branch(condition_is_overflow, overflow_block, non_overflow_block)?;
context.set_basic_block(overflow_block);
context.build_store(result_pointer, context.field_const(0))?;
context.build_unconditional_branch(join_block);
context.set_basic_block(non_overflow_block);
let value = context.builder().build_right_shift(
value,
shift,
false,
"shift_right_non_overflow_result",
)?;
context.build_store(result_pointer, value)?;
context.build_unconditional_branch(join_block);
context.set_basic_block(join_block);
context.build_load(result_pointer, "shift_right_result")
}
///
/// Translates the arithmetic bitwise shift right.
///
pub fn shift_right_arithmetic<'ctx, D>(
context: &mut Context<'ctx, D>,
shift: inkwell::values::IntValue<'ctx>,
value: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let overflow_block = context.append_basic_block("shift_right_arithmetic_overflow");
let overflow_positive_block =
context.append_basic_block("shift_right_arithmetic_overflow_positive");
let overflow_negative_block =
context.append_basic_block("shift_right_arithmetic_overflow_negative");
let non_overflow_block = context.append_basic_block("shift_right_arithmetic_non_overflow");
let join_block = context.append_basic_block("shift_right_arithmetic_join");
let result_pointer = context.build_alloca(
context.field_type(),
"shift_right_arithmetic_result_pointer",
);
let condition_is_overflow = context.builder().build_int_compare(
inkwell::IntPredicate::UGT,
shift,
context.field_const((era_compiler_common::BIT_LENGTH_FIELD - 1) as u64),
"shift_right_arithmetic_is_overflow",
)?;
context.build_conditional_branch(condition_is_overflow, overflow_block, non_overflow_block)?;
context.set_basic_block(overflow_block);
let sign_bit = context.builder().build_right_shift(
value,
context.field_const((era_compiler_common::BIT_LENGTH_FIELD - 1) as u64),
false,
"shift_right_arithmetic_sign_bit",
)?;
let condition_is_negative = context.builder().build_int_truncate_or_bit_cast(
sign_bit,
context.bool_type(),
"shift_right_arithmetic_sign_bit_truncated",
)?;
context.build_conditional_branch(
condition_is_negative,
overflow_negative_block,
overflow_positive_block,
)?;
context.set_basic_block(overflow_positive_block);
context.build_store(result_pointer, context.field_const(0))?;
context.build_unconditional_branch(join_block);
context.set_basic_block(overflow_negative_block);
context.build_store(result_pointer, context.field_type().const_all_ones())?;
context.build_unconditional_branch(join_block);
context.set_basic_block(non_overflow_block);
let value = context.builder().build_right_shift(
value,
shift,
true,
"shift_right_arithmetic_non_overflow_result",
)?;
context.build_store(result_pointer, value)?;
context.build_unconditional_branch(join_block);
context.set_basic_block(join_block);
context.build_load(result_pointer, "shift_right_arithmetic_result")
}
///
/// Translates the `byte` instruction.
///
pub fn byte<'ctx, D>(
context: &mut Context<'ctx, D>,
operand_1: inkwell::values::IntValue<'ctx>,
operand_2: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context
.build_call(
context.llvm_runtime().byte,
&[
operand_1.as_basic_value_enum(),
operand_2.as_basic_value_enum(),
],
"byte_call",
)
.expect("Always exists"))
}
+756
View File
@@ -0,0 +1,756 @@
//!
//! Translates a contract call.
//!
use inkwell::values::BasicValue;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::argument::Argument;
use crate::eravm::context::function::declaration::Declaration as FunctionDeclaration;
use crate::eravm::context::function::runtime::Runtime;
use crate::eravm::context::pointer::Pointer;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Translates a contract call.
///
/// If the `simulation_address` is specified, the call is substituted with another instruction
/// according to the specification.
///
#[allow(clippy::too_many_arguments)]
pub fn default<'ctx, D>(
_context: &mut Context<'ctx, D>,
_function: FunctionDeclaration<'ctx>,
_gas: inkwell::values::IntValue<'ctx>,
_address: inkwell::values::IntValue<'ctx>,
_value: Option<inkwell::values::IntValue<'ctx>>,
_input_offset: inkwell::values::IntValue<'ctx>,
_input_length: inkwell::values::IntValue<'ctx>,
_output_offset: inkwell::values::IntValue<'ctx>,
_output_length: inkwell::values::IntValue<'ctx>,
_constants: Vec<Option<num::BigUint>>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
todo!();
/*
if context.is_system_mode() {
let simulation_address = constants
.get_mut(1)
.and_then(|option| option.take())
.and_then(|value| value.to_u16());
match simulation_address {
Some(era_compiler_common::ERAVM_ADDRESS_TO_L1) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().far_call,
function,
"to_l1",
)?;
let is_first = gas;
let in_0 = value.expect("Always exists");
let in_1 = input_offset;
return crate::eravm::extensions::general::to_l1(context, is_first, in_0, in_1);
}
Some(era_compiler_common::ERAVM_ADDRESS_CODE_ADDRESS) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"code_address",
)?;
return crate::eravm::extensions::general::code_source(context);
}
Some(era_compiler_common::ERAVM_ADDRESS_PRECOMPILE) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"precompile",
)?;
let in_0 = gas;
let gas_left = input_offset;
return crate::eravm::extensions::general::precompile(context, in_0, gas_left);
}
Some(era_compiler_common::ERAVM_ADDRESS_META) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"meta",
)?;
return crate::eravm::extensions::general::meta(context);
}
Some(era_compiler_common::ERAVM_ADDRESS_MIMIC_CALL) => {
let address = gas;
let abi_data = input_offset;
let mimic = input_length;
return crate::eravm::extensions::call::mimic(
context,
context.llvm_runtime().mimic_call,
address,
mimic,
abi_data.as_basic_value_enum(),
vec![],
);
}
Some(era_compiler_common::ERAVM_ADDRESS_SYSTEM_MIMIC_CALL) => {
let address = gas;
let abi_data = input_offset;
let mimic = input_length;
let extra_value_1 = output_offset;
let extra_value_2 = output_length;
return crate::eravm::extensions::call::mimic(
context,
context.llvm_runtime().mimic_call,
address,
mimic,
abi_data.as_basic_value_enum(),
vec![extra_value_1, extra_value_2],
);
}
Some(era_compiler_common::ERAVM_ADDRESS_MIMIC_CALL_BYREF) => {
let address = gas;
let mimic = input_length;
let abi_data = context.get_global_value(crate::eravm::GLOBAL_ACTIVE_POINTER)?;
return crate::eravm::extensions::call::mimic(
context,
context.llvm_runtime().mimic_call_byref,
address,
mimic,
abi_data.as_basic_value_enum(),
vec![],
);
}
Some(era_compiler_common::ERAVM_ADDRESS_SYSTEM_MIMIC_CALL_BYREF) => {
let address = gas;
let mimic = input_length;
let abi_data = context.get_global_value(crate::eravm::GLOBAL_ACTIVE_POINTER)?;
let extra_value_1 = output_offset;
let extra_value_2 = output_length;
return crate::eravm::extensions::call::mimic(
context,
context.llvm_runtime().mimic_call_byref,
address,
mimic,
abi_data,
vec![extra_value_1, extra_value_2],
);
}
Some(era_compiler_common::ERAVM_ADDRESS_RAW_FAR_CALL) => {
let address = gas;
let abi_data = input_length;
return crate::eravm::extensions::call::raw_far(
context,
context.llvm_runtime().modify(function, false)?,
address,
abi_data.as_basic_value_enum(),
output_offset,
output_length,
);
}
Some(era_compiler_common::ERAVM_ADDRESS_RAW_FAR_CALL_BYREF) => {
let address = gas;
let abi_data = context.get_global_value(crate::eravm::GLOBAL_ACTIVE_POINTER)?;
return crate::eravm::extensions::call::raw_far(
context,
context.llvm_runtime().modify(function, true)?,
address,
abi_data,
output_offset,
output_length,
);
}
Some(era_compiler_common::ERAVM_ADDRESS_SYSTEM_CALL) => {
let address = gas;
let abi_data = input_length;
let extra_value_1 = value.expect("Always exists");
let extra_value_2 = input_offset;
let extra_value_3 = output_offset;
let extra_value_4 = output_length;
return crate::eravm::extensions::call::system(
context,
context.llvm_runtime().modify(function, false)?,
address,
abi_data.as_basic_value_enum(),
context.field_const(0),
context.field_const(0),
vec![extra_value_1, extra_value_2, extra_value_3, extra_value_4],
);
}
Some(era_compiler_common::ERAVM_ADDRESS_SYSTEM_CALL_BYREF) => {
let address = gas;
let abi_data = context.get_global_value(crate::eravm::GLOBAL_ACTIVE_POINTER)?;
let extra_value_1 = value.expect("Always exists");
let extra_value_2 = input_offset;
let extra_value_3 = output_offset;
let extra_value_4 = output_length;
return crate::eravm::extensions::call::system(
context,
context.llvm_runtime().modify(function, true)?,
address,
abi_data,
context.field_const(0),
context.field_const(0),
vec![extra_value_1, extra_value_2, extra_value_3, extra_value_4],
);
}
Some(era_compiler_common::ERAVM_ADDRESS_SET_CONTEXT_VALUE_CALL) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().far_call,
function,
"set_context_value",
)?;
let value = value.expect("Always exists");
return crate::eravm::extensions::general::set_context_value(context, value);
}
Some(era_compiler_common::ERAVM_ADDRESS_SET_PUBDATA_PRICE) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().far_call,
function,
"set_pubdata_price",
)?;
let price = gas;
return crate::eravm::extensions::general::set_pubdata_price(context, price);
}
Some(era_compiler_common::ERAVM_ADDRESS_INCREMENT_TX_COUNTER) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().far_call,
function,
"increment_tx_counter",
)?;
return crate::eravm::extensions::general::increment_tx_counter(context);
}
Some(era_compiler_common::ERAVM_ADDRESS_GET_GLOBAL_PTR_CALLDATA) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"get_global_ptr_calldata",
)?;
let pointer = context.get_global_value(crate::eravm::GLOBAL_CALLDATA_POINTER)?;
let value = context.builder().build_ptr_to_int(
pointer.into_pointer_value(),
context.field_type(),
"calldata_abi_integer",
)?;
return Ok(value.as_basic_value_enum());
}
Some(era_compiler_common::ERAVM_ADDRESS_GET_GLOBAL_CALL_FLAGS) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"get_global_call_flags",
)?;
return context.get_global_value(crate::eravm::GLOBAL_CALL_FLAGS);
}
Some(era_compiler_common::ERAVM_ADDRESS_GET_GLOBAL_PTR_RETURN_DATA) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"get_global_ptr_return_data",
)?;
let pointer = context.get_global_value(crate::eravm::GLOBAL_RETURN_DATA_POINTER)?;
let value = context.builder().build_ptr_to_int(
pointer.into_pointer_value(),
context.field_type(),
"return_data_abi_integer",
)?;
return Ok(value.as_basic_value_enum());
}
Some(era_compiler_common::ERAVM_ADDRESS_EVENT_INITIALIZE) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().far_call,
function,
"event_initialize",
)?;
let operand_1 = gas;
let operand_2 = value.expect("Always exists");
return crate::eravm::extensions::general::event(
context, operand_1, operand_2, true,
);
}
Some(era_compiler_common::ERAVM_ADDRESS_EVENT_WRITE) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().far_call,
function,
"event_initialize",
)?;
let operand_1 = gas;
let operand_2 = value.expect("Always exists");
return crate::eravm::extensions::general::event(
context, operand_1, operand_2, false,
);
}
Some(era_compiler_common::ERAVM_ADDRESS_ACTIVE_PTR_LOAD_CALLDATA) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"active_ptr_load_calldata",
)?;
return crate::eravm::extensions::abi::calldata_ptr_to_active(context);
}
Some(era_compiler_common::ERAVM_ADDRESS_ACTIVE_PTR_LOAD_RETURN_DATA) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"active_ptr_load_return_data",
)?;
return crate::eravm::extensions::abi::return_data_ptr_to_active(context);
}
Some(era_compiler_common::ERAVM_ADDRESS_ACTIVE_PTR_ADD) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"active_ptr_add",
)?;
let offset = gas;
return crate::eravm::extensions::abi::active_ptr_add_assign(context, offset);
}
Some(era_compiler_common::ERAVM_ADDRESS_ACTIVE_PTR_SHRINK) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"active_ptr_shrink",
)?;
let offset = gas;
return crate::eravm::extensions::abi::active_ptr_shrink_assign(context, offset);
}
Some(era_compiler_common::ERAVM_ADDRESS_ACTIVE_PTR_PACK) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"active_ptr_pack",
)?;
let data = gas;
return crate::eravm::extensions::abi::active_ptr_pack_assign(context, data);
}
Some(era_compiler_common::ERAVM_ADDRESS_MULTIPLICATION_HIGH_REGISTER) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"multiplication_high_register",
)?;
let operand_1 = gas;
let operand_2 = input_offset;
return crate::eravm::extensions::math::multiplication_512(
context, operand_1, operand_2,
);
}
Some(era_compiler_common::ERAVM_ADDRESS_GET_GLOBAL_EXTRA_ABI_DATA) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"get_global_extra_abi_data",
)?;
let index = gas;
return crate::eravm::extensions::abi::get_extra_abi_data(context, index);
}
Some(era_compiler_common::ERAVM_ADDRESS_ACTIVE_PTR_DATA_LOAD) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"active_ptr_data_load",
)?;
let offset = gas;
return crate::eravm::extensions::abi::active_ptr_data_load(context, offset);
}
Some(era_compiler_common::ERAVM_ADDRESS_ACTIVE_PTR_DATA_SIZE) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"active_ptr_data_size",
)?;
return crate::eravm::extensions::abi::active_ptr_data_size(context);
}
Some(era_compiler_common::ERAVM_ADDRESS_ACTIVE_PTR_DATA_COPY) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"active_ptr_data_copy",
)?;
let destination_offset = gas;
let source_offset = input_offset;
let size = input_length;
return crate::eravm::extensions::abi::active_ptr_data_copy(
context,
destination_offset,
source_offset,
size,
);
}
Some(era_compiler_common::ERAVM_ADDRESS_CONST_ARRAY_DECLARE) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"const_array_declare",
)?;
let index = constants
.get_mut(0)
.and_then(|option| option.take())
.ok_or_else(|| anyhow::anyhow!("Const array index is missing"))?
.to_u8()
.ok_or_else(|| anyhow::anyhow!("Const array index must fit into 8 bits"))?;
let size = constants
.get_mut(2)
.and_then(|option| option.take())
.ok_or_else(|| anyhow::anyhow!("Const array size is missing"))?
.to_u16()
.ok_or_else(|| anyhow::anyhow!("Const array size must fit into 16 bits"))?;
return crate::eravm::extensions::const_array::declare(context, index, size);
}
Some(era_compiler_common::ERAVM_ADDRESS_CONST_ARRAY_SET) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"const_array_set",
)?;
let index = constants
.get_mut(0)
.and_then(|option| option.take())
.ok_or_else(|| anyhow::anyhow!("Const array index is missing"))?
.to_u8()
.ok_or_else(|| anyhow::anyhow!("Const array index must fit into 8 bits"))?;
let offset = constants
.get_mut(2)
.and_then(|option| option.take())
.ok_or_else(|| anyhow::anyhow!("Const array offset is missing"))?
.to_u16()
.ok_or_else(|| anyhow::anyhow!("Const array offset must fit into 16 bits"))?;
let value = constants
.get_mut(4)
.and_then(|option| option.take())
.ok_or_else(|| anyhow::anyhow!("Const array assigned value is missing"))?;
return crate::eravm::extensions::const_array::set(context, index, offset, value);
}
Some(era_compiler_common::ERAVM_ADDRESS_CONST_ARRAY_FINALIZE) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"const_array_finalize",
)?;
let index = constants
.get_mut(0)
.and_then(|option| option.take())
.ok_or_else(|| anyhow::anyhow!("Const array index is missing"))?
.to_u8()
.ok_or_else(|| anyhow::anyhow!("Const array index must fit into 8 bits"))?;
return crate::eravm::extensions::const_array::finalize(context, index);
}
Some(era_compiler_common::ERAVM_ADDRESS_CONST_ARRAY_GET) => {
crate::eravm::extensions::call::validate_call_type(
context.llvm_runtime().static_call,
function,
"const_array_get",
)?;
let index = constants
.get_mut(0)
.and_then(|option| option.take())
.ok_or_else(|| anyhow::anyhow!("Const array index is missing"))?
.to_u8()
.ok_or_else(|| anyhow::anyhow!("Const array index must fit into 8 bits"))?;
let offset = input_offset;
return crate::eravm::extensions::const_array::get(context, index, offset);
}
_ => {}
}
}
let identity_block = context.append_basic_block("contract_call_identity_block");
let ordinary_block = context.append_basic_block("contract_call_ordinary_block");
let join_block = context.append_basic_block("contract_call_join_block");
let result_pointer = context.build_alloca(context.field_type(), "contract_call_result_pointer");
context.build_store(result_pointer, context.field_const(0));
context.builder().build_switch(
address,
ordinary_block,
&[(
context.field_const(zkevm_opcode_defs::ADDRESS_IDENTITY.into()),
identity_block,
)],
)?;
{
context.set_basic_block(identity_block);
let result = identity(context, output_offset, input_offset, output_length)?;
context.build_store(result_pointer, result);
context.build_unconditional_branch(join_block);
}
context.set_basic_block(ordinary_block);
let result = if let Some(value) = value {
default_wrapped(
context,
function,
gas,
value,
address,
input_offset,
input_length,
output_offset,
output_length,
)?
} else {
let function = Runtime::default_call(context, function);
context
.build_call(
function,
&[
gas.as_basic_value_enum(),
address.as_basic_value_enum(),
input_offset.as_basic_value_enum(),
input_length.as_basic_value_enum(),
output_offset.as_basic_value_enum(),
output_length.as_basic_value_enum(),
],
"default_call",
)
.expect("Always exists")
};
context.build_store(result_pointer, result);
context.build_unconditional_branch(join_block);
context.set_basic_block(join_block);
let result = context.build_load(result_pointer, "contract_call_result");
Ok(result)
*/
}
///
/// Translates the Yul `linkersymbol` instruction.
///
pub fn linker_symbol<'ctx, D>(
context: &mut Context<'ctx, D>,
mut arguments: [Argument<'ctx>; 1],
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let path = arguments[0]
.original
.take()
.ok_or_else(|| anyhow::anyhow!("Linker symbol literal is missing"))?;
Ok(context
.resolve_library(path.as_str())?
.as_basic_value_enum())
}
///
/// Generates a custom request to a system contract.
///
pub fn request<'ctx, D>(
context: &mut Context<'ctx, D>,
address: inkwell::values::IntValue<'ctx>,
signature: &'static str,
arguments: Vec<inkwell::values::IntValue<'ctx>>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let signature_hash = crate::eravm::utils::keccak256(signature.as_bytes());
let signature_value = context.field_const_str_hex(signature_hash.as_str());
let calldata_size = context.field_const(
(era_compiler_common::BYTE_LENGTH_X32
+ (era_compiler_common::BYTE_LENGTH_FIELD * arguments.len())) as u64,
);
let calldata_array_pointer = context.build_alloca(
context.array_type(context.field_type(), arguments.len()),
"system_request_calldata_array_pointer",
);
for (index, argument) in arguments.into_iter().enumerate() {
let argument_pointer = context.build_gep(
calldata_array_pointer,
&[context.field_const(0), context.field_const(index as u64)],
context.field_type(),
"system_request_calldata_array_pointer",
);
context.build_store(argument_pointer, argument)?;
}
Ok(context
.build_invoke(
context.llvm_runtime().system_request,
&[
address.as_basic_value_enum(),
signature_value.as_basic_value_enum(),
calldata_size.as_basic_value_enum(),
calldata_array_pointer.value.as_basic_value_enum(),
],
"system_request_call",
)
.expect("Always exists"))
}
///
/// The default call wrapper, which redirects the call to the `msg.value` simulator if `msg.value`
/// is not zero.
///
#[allow(clippy::too_many_arguments)]
fn _default_wrapped<'ctx, D>(
context: &mut Context<'ctx, D>,
function: FunctionDeclaration<'ctx>,
gas: inkwell::values::IntValue<'ctx>,
value: inkwell::values::IntValue<'ctx>,
address: inkwell::values::IntValue<'ctx>,
input_offset: inkwell::values::IntValue<'ctx>,
input_length: inkwell::values::IntValue<'ctx>,
output_offset: inkwell::values::IntValue<'ctx>,
output_length: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let value_zero_block = context.append_basic_block("contract_call_value_zero_block");
let value_non_zero_block = context.append_basic_block("contract_call_value_non_zero_block");
let value_join_block = context.append_basic_block("contract_call_value_join_block");
let result_pointer =
context.build_alloca(context.field_type(), "contract_call_address_result_pointer");
context.build_store(result_pointer, context.field_const(0))?;
let is_value_zero = context.builder().build_int_compare(
inkwell::IntPredicate::EQ,
value,
context.field_const(0),
"contract_call_is_value_zero",
)?;
context.build_conditional_branch(is_value_zero, value_zero_block, value_non_zero_block)?;
context.set_basic_block(value_non_zero_block);
let abi_data = crate::eravm::utils::abi_data(
context,
input_offset,
input_length,
Some(gas),
AddressSpace::Heap,
true,
)?;
let result = crate::eravm::extensions::call::system(
context,
context.llvm_runtime().modify(function, false)?,
context.field_const(zkevm_opcode_defs::ADDRESS_MSG_VALUE.into()),
abi_data,
output_offset,
output_length,
vec![
value,
address,
context.field_const(u64::from(crate::eravm::r#const::NO_SYSTEM_CALL_BIT)),
],
)?;
context.build_store(result_pointer, result)?;
context.build_unconditional_branch(value_join_block);
context.set_basic_block(value_zero_block);
let function = Runtime::default_call(context, function);
let result = context
.build_call(
function,
&[
gas.as_basic_value_enum(),
address.as_basic_value_enum(),
input_offset.as_basic_value_enum(),
input_length.as_basic_value_enum(),
output_offset.as_basic_value_enum(),
output_length.as_basic_value_enum(),
],
"default_call",
)
.expect("Always exists");
context.build_store(result_pointer, result)?;
context.build_unconditional_branch(value_join_block);
context.set_basic_block(value_join_block);
context.build_load(result_pointer, "contract_call_address_result")
}
///
/// Generates a memory copy loop repeating the behavior of the EVM `Identity` precompile.
///
fn _identity<'ctx, D>(
context: &mut Context<'ctx, D>,
destination: inkwell::values::IntValue<'ctx>,
source: inkwell::values::IntValue<'ctx>,
size: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let destination = Pointer::new_with_offset(
context,
AddressSpace::Heap,
context.byte_type(),
destination,
"contract_call_identity_destination",
);
let source = Pointer::new_with_offset(
context,
AddressSpace::Heap,
context.byte_type(),
source,
"contract_call_identity_source",
);
context.build_memcpy(
context.intrinsics().memory_copy,
destination,
source,
size,
"contract_call_memcpy_to_child",
)?;
Ok(context.field_const(1).as_basic_value_enum())
}
@@ -0,0 +1,88 @@
//!
//! Translates the calldata instructions.
//!
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::pointer::Pointer;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
use inkwell::types::BasicType;
///
/// Translates the calldata load.
///
pub fn load<'ctx, D>(
context: &mut Context<'ctx, D>,
offset: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let calldata_pointer = context
.get_global(crate::eravm::GLOBAL_CALLDATA_POINTER)?
.value
.as_pointer_value();
let offset = context.build_gep(
Pointer::new(context.byte_type(), AddressSpace::Stack, calldata_pointer),
&[offset],
context.field_type().as_basic_type_enum(),
"calldata_pointer_with_offset",
);
context
.build_load(offset, "calldata_value")
.map(|value| context.build_byte_swap(value))
}
///
/// Translates the calldata size.
///
pub fn size<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let value = context.get_global_value(crate::eravm::GLOBAL_CALLDATA_SIZE)?;
Ok(value)
}
///
/// Translates the calldata copy.
///
pub fn copy<'ctx, D>(
context: &mut Context<'ctx, D>,
destination_offset: inkwell::values::IntValue<'ctx>,
source_offset: inkwell::values::IntValue<'ctx>,
size: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
// TODO: Untested
let destination = Pointer::new_with_offset(
context,
AddressSpace::Heap,
context.byte_type(),
destination_offset,
"calldata_copy_destination_pointer",
);
let calldata_pointer = context
.get_global(crate::eravm::GLOBAL_CALLDATA_POINTER)?
.value
.as_pointer_value();
let source = context.build_gep(
Pointer::new(context.byte_type(), AddressSpace::Stack, calldata_pointer),
&[source_offset],
context.field_type().as_basic_type_enum(),
"calldata_pointer_with_offset",
);
context.build_memcpy(
context.intrinsics().memory_copy_from_generic,
destination,
source,
size,
"calldata_copy_memcpy_from_child",
)
}
@@ -0,0 +1,36 @@
//!
//! Translates the comparison operations.
//!
use inkwell::values::BasicValue;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Translates the comparison operations.
///
/// There is not difference between the EVM and LLVM IR behaviors.
///
pub fn compare<'ctx, D>(
context: &mut Context<'ctx, D>,
operand_1: inkwell::values::IntValue<'ctx>,
operand_2: inkwell::values::IntValue<'ctx>,
operation: inkwell::IntPredicate,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let result = context.builder().build_int_compare(
operation,
operand_1,
operand_2,
"comparison_result",
)?;
let result = context.builder().build_int_z_extend_or_bit_cast(
result,
context.field_type(),
"comparison_result_extended",
)?;
Ok(result.as_basic_value_enum())
}
@@ -0,0 +1,189 @@
//!
//! Translates the context getter instructions.
//!
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Translates the `gas_limit` instruction.
///
pub fn gas_limit<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
crate::eravm::evm::call::request(
context,
context.field_const(zkevm_opcode_defs::ADDRESS_SYSTEM_CONTEXT.into()),
"blockGasLimit()",
vec![],
)
}
///
/// Translates the `gas_price` instruction.
///
pub fn gas_price<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
crate::eravm::evm::call::request(
context,
context.field_const(zkevm_opcode_defs::ADDRESS_SYSTEM_CONTEXT.into()),
"gasPrice()",
vec![],
)
}
///
/// Translates the `tx.origin` instruction.
///
pub fn origin<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
crate::eravm::evm::call::request(
context,
context.field_const(zkevm_opcode_defs::ADDRESS_SYSTEM_CONTEXT.into()),
"origin()",
vec![],
)
}
///
/// Translates the `chain_id` instruction.
///
pub fn chain_id<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
crate::eravm::evm::call::request(
context,
context.field_const(zkevm_opcode_defs::ADDRESS_SYSTEM_CONTEXT.into()),
"chainId()",
vec![],
)
}
///
/// Translates the `block_number` instruction.
///
pub fn block_number<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
crate::eravm::evm::call::request(
context,
context.field_const(zkevm_opcode_defs::ADDRESS_SYSTEM_CONTEXT.into()),
"getBlockNumber()",
vec![],
)
}
///
/// Translates the `block_timestamp` instruction.
///
pub fn block_timestamp<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
crate::eravm::evm::call::request(
context,
context.field_const(zkevm_opcode_defs::ADDRESS_SYSTEM_CONTEXT.into()),
"getBlockTimestamp()",
vec![],
)
}
///
/// Translates the `block_hash` instruction.
///
pub fn block_hash<'ctx, D>(
context: &mut Context<'ctx, D>,
index: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
crate::eravm::evm::call::request(
context,
context.field_const(zkevm_opcode_defs::ADDRESS_SYSTEM_CONTEXT.into()),
"getBlockHashEVM(uint256)",
vec![index],
)
}
///
/// Translates the `difficulty` instruction.
///
pub fn difficulty<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
crate::eravm::evm::call::request(
context,
context.field_const(zkevm_opcode_defs::ADDRESS_SYSTEM_CONTEXT.into()),
"difficulty()",
vec![],
)
}
///
/// Translates the `coinbase` instruction.
///
pub fn coinbase<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
crate::eravm::evm::call::request(
context,
context.field_const(zkevm_opcode_defs::ADDRESS_SYSTEM_CONTEXT.into()),
"coinbase()",
vec![],
)
}
///
/// Translates the `basefee` instruction.
///
pub fn basefee<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
crate::eravm::evm::call::request(
context,
context.field_const(zkevm_opcode_defs::ADDRESS_SYSTEM_CONTEXT.into()),
"baseFee()",
vec![],
)
}
///
/// Translates the `msize` instruction.
///
pub fn msize<'ctx, D>(
_context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
todo!()
}
+185
View File
@@ -0,0 +1,185 @@
//!
//! Translates the contract creation instructions.
//!
use inkwell::values::BasicValue;
use num::Zero;
use crate::eravm::context::argument::Argument;
use crate::eravm::context::code_type::CodeType;
use crate::eravm::context::function::runtime::Runtime;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Translates the contract `create` instruction.
///
/// The instruction is simulated by a call to a system contract.
///
pub fn create<'ctx, D>(
context: &mut Context<'ctx, D>,
value: inkwell::values::IntValue<'ctx>,
input_offset: inkwell::values::IntValue<'ctx>,
input_length: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let signature_hash_string =
crate::eravm::utils::keccak256(crate::eravm::DEPLOYER_SIGNATURE_CREATE.as_bytes());
let signature_hash = context.field_const_str_hex(signature_hash_string.as_str());
let salt = context.field_const(0);
let function = Runtime::deployer_call(context);
let result = context
.build_call(
function,
&[
value.as_basic_value_enum(),
input_offset.as_basic_value_enum(),
input_length.as_basic_value_enum(),
signature_hash.as_basic_value_enum(),
salt.as_basic_value_enum(),
],
"create_deployer_call",
)
.expect("Always exists");
Ok(result)
}
///
/// Translates the contract `create2` instruction.
///
/// The instruction is simulated by a call to a system contract.
///
pub fn create2<'ctx, D>(
context: &mut Context<'ctx, D>,
value: inkwell::values::IntValue<'ctx>,
input_offset: inkwell::values::IntValue<'ctx>,
input_length: inkwell::values::IntValue<'ctx>,
salt: Option<inkwell::values::IntValue<'ctx>>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let signature_hash_string =
crate::eravm::utils::keccak256(crate::eravm::DEPLOYER_SIGNATURE_CREATE2.as_bytes());
let signature_hash = context.field_const_str_hex(signature_hash_string.as_str());
let salt = salt.unwrap_or_else(|| context.field_const(0));
let function = Runtime::deployer_call(context);
let result = context
.build_call(
function,
&[
value.as_basic_value_enum(),
input_offset.as_basic_value_enum(),
input_length.as_basic_value_enum(),
signature_hash.as_basic_value_enum(),
salt.as_basic_value_enum(),
],
"create2_deployer_call",
)
.expect("Always exists");
Ok(result)
}
///
/// Translates the contract hash instruction, which is actually used to set the hash of the contract
/// being created, or other related auxiliary data.
///
/// Represents `dataoffset` in Yul and `PUSH [$]` in the EVM legacy assembly.
///
pub fn contract_hash<'ctx, D>(
context: &mut Context<'ctx, D>,
identifier: String,
) -> anyhow::Result<Argument<'ctx>>
where
D: Dependency + Clone,
{
let code_type = context
.code_type()
.ok_or_else(|| anyhow::anyhow!("The contract code part type is undefined"))?;
let parent = context.module().get_name().to_str().expect("Always valid");
let contract_path =
context
.resolve_path(identifier.as_str())
.map_err(|error| match code_type {
CodeType::Runtime if identifier.ends_with("_deployed") => {
anyhow::anyhow!("type({}).runtimeCode is not supported", identifier)
}
_ => error,
})?;
if contract_path.as_str() == parent {
return Ok(Argument::new_with_constant(
context.field_const(0).as_basic_value_enum(),
num::BigUint::zero(),
));
} else if identifier.ends_with("_deployed") && code_type == CodeType::Runtime {
anyhow::bail!("type({}).runtimeCode is not supported", identifier);
}
let hash_string = context.compile_dependency(identifier.as_str())?;
let hash_value = context
.field_const_str_hex(hash_string.as_str())
.as_basic_value_enum();
Ok(Argument::new_with_original(hash_value, hash_string))
}
///
/// Translates the deployer call header size instruction, Usually, the header consists of:
/// - the deployer contract method signature
/// - the salt if the call is `create2`, or zero if the call is `create1`
/// - the hash of the bytecode of the contract whose instance is being created
/// - the offset of the constructor arguments
/// - the length of the constructor arguments
///
/// If the call is `create1`, the space for the salt is still allocated, because the memory for the
/// header is allocated by the Yul or EVM legacy assembly before it is known which version of
/// `create` is going to be used.
///
/// Represents `datasize` in Yul and `PUSH #[$]` in the EVM legacy assembly.
///
pub fn header_size<'ctx, D>(
context: &mut Context<'ctx, D>,
identifier: String,
) -> anyhow::Result<Argument<'ctx>>
where
D: Dependency + Clone,
{
let code_type = context
.code_type()
.ok_or_else(|| anyhow::anyhow!("The contract code part type is undefined"))?;
let parent = context.module().get_name().to_str().expect("Always valid");
let contract_path =
context
.resolve_path(identifier.as_str())
.map_err(|error| match code_type {
CodeType::Runtime if identifier.ends_with("_deployed") => {
anyhow::anyhow!("type({}).runtimeCode is not supported", identifier)
}
_ => error,
})?;
if contract_path.as_str() == parent {
return Ok(Argument::new_with_constant(
context.field_const(0).as_basic_value_enum(),
num::BigUint::zero(),
));
} else if identifier.ends_with("_deployed") && code_type == CodeType::Runtime {
anyhow::bail!("type({}).runtimeCode is not supported", identifier);
}
let size_bigint = num::BigUint::from(crate::eravm::DEPLOYER_CALL_HEADER_SIZE);
let size_value = context
.field_const(crate::eravm::DEPLOYER_CALL_HEADER_SIZE as u64)
.as_basic_value_enum();
Ok(Argument::new_with_constant(size_value, size_bigint))
}
@@ -0,0 +1,49 @@
//!
//! Translates the cryptographic operations.
//!
use inkwell::values::BasicValue;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::function::Function as EraVMFunction;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Translates the `sha3` instruction.
///
pub fn sha3<'ctx, D>(
context: &mut Context<'ctx, D>,
offset: inkwell::values::IntValue<'ctx>,
length: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(offset.into())
/*
let offset_pointer = context.builder().build_int_to_ptr(
offset,
context.byte_type().ptr_type(AddressSpace::Heap.into()),
"sha3_offset_pointer",
)?;
Ok(context
.build_invoke(
context.llvm_runtime().sha3,
&[
offset_pointer.as_basic_value_enum(),
length.as_basic_value_enum(),
context
.bool_const(
context
.get_function(EraVMFunction::ZKSYNC_NEAR_CALL_ABI_EXCEPTION_HANDLER)
.is_some(),
)
.as_basic_value_enum(),
],
"sha3_call",
)
.expect("Always exists"))
*/
}
@@ -0,0 +1,50 @@
//!
//! Translates the value and balance operations.
//!
use inkwell::values::BasicValue;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Translates the `gas` instruction.
///
pub fn gas<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context.integer_const(256, 0).as_basic_value_enum())
}
///
/// Translates the `value` instruction.
///
pub fn value<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context.integer_const(256, 0).as_basic_value_enum())
}
///
/// Translates the `balance` instructions.
///
pub fn balance<'ctx, D>(
context: &mut Context<'ctx, D>,
address: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
crate::eravm::evm::call::request(
context,
context.field_const(zkevm_opcode_defs::ADDRESS_ETH_TOKEN.into()),
"balanceOf(uint256)",
vec![address],
)
}
@@ -0,0 +1,81 @@
//!
//! Translates a log or event call.
//!
use inkwell::values::BasicValue;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Translates a log or event call.
///
/// The decoding logic is implemented in a system contract, which is called from here.
///
/// There are several cases of the translation for the sake of efficiency, since the front-end
/// emits topics and values sequentially by one, but the LLVM intrinsic and bytecode instruction
/// accept two at once.
///
pub fn log<'ctx, D>(
context: &mut Context<'ctx, D>,
input_offset: inkwell::values::IntValue<'ctx>,
input_length: inkwell::values::IntValue<'ctx>,
topics: Vec<inkwell::values::IntValue<'ctx>>,
) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
/*
let failure_block = context.append_basic_block("event_failure_block");
let join_block = context.append_basic_block("event_join_block");
let gas = crate::eravm::evm::ether_gas::gas(context)?.into_int_value();
let abi_data = crate::eravm::utils::abi_data(
context,
input_offset,
input_length,
Some(gas),
AddressSpace::Heap,
true,
)?;
let mut extra_abi_data = Vec::with_capacity(1 + topics.len());
extra_abi_data.push(context.field_const(topics.len() as u64));
extra_abi_data.extend(topics);
let result = context
.build_call(
context.llvm_runtime().far_call,
crate::eravm::utils::external_call_arguments(
context,
abi_data.as_basic_value_enum(),
context.field_const(zkevm_opcode_defs::ADDRESS_EVENT_WRITER as u64),
extra_abi_data,
None,
)
.as_slice(),
"event_writer_call_external",
)
.expect("Always returns a value");
let result_status_code_boolean = context
.builder()
.build_extract_value(
result.into_struct_value(),
1,
"event_writer_external_result_status_code_boolean",
)
.expect("Always exists");
context.build_conditional_branch(
result_status_code_boolean.into_int_value(),
join_block,
failure_block,
)?;
context.set_basic_block(failure_block);
crate::eravm::evm::r#return::revert(context, context.field_const(0), context.field_const(0))?;
context.set_basic_block(join_block);
*/
Ok(())
}
@@ -0,0 +1,42 @@
//!
//! Translates the external code operations.
//!
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Translates the `extcodesize` instruction.
///
pub fn size<'ctx, D>(
context: &mut Context<'ctx, D>,
address: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
crate::eravm::evm::call::request(
context,
context.field_const(zkevm_opcode_defs::ADDRESS_ACCOUNT_CODE_STORAGE.into()),
"getCodeSize(uint256)",
vec![address],
)
}
///
/// Translates the `extcodehash` instruction.
///
pub fn hash<'ctx, D>(
context: &mut Context<'ctx, D>,
address: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
crate::eravm::evm::call::request(
context,
context.field_const(zkevm_opcode_defs::ADDRESS_ACCOUNT_CODE_STORAGE.into()),
"getCodeHash(uint256)",
vec![address],
)
}
@@ -0,0 +1,120 @@
//!
//! Translates the contract immutable operations.
//!
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::code_type::CodeType;
use crate::eravm::context::pointer::Pointer;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Translates the contract immutable load.
///
/// In the deploy code the values are read from the auxiliary heap.
/// In the runtime code they are requested from the system contract.
///
pub fn load<'ctx, D>(
context: &mut Context<'ctx, D>,
index: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
match context.code_type() {
None => {
anyhow::bail!("Immutables are not available if the contract part is undefined");
}
Some(CodeType::Deploy) => {
let index_double = context.builder().build_int_mul(
index,
context.field_const(2),
"immutable_load_index_double",
)?;
let offset_absolute = context.builder().build_int_add(
index_double,
context.field_const(
crate::eravm::HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA
+ (3 * era_compiler_common::BYTE_LENGTH_FIELD) as u64,
),
"immutable_offset_absolute",
)?;
let immutable_pointer = Pointer::new_with_offset(
context,
AddressSpace::HeapAuxiliary,
context.field_type(),
offset_absolute,
"immutable_pointer",
);
context.build_load(immutable_pointer, "immutable_value")
}
Some(CodeType::Runtime) => {
todo!()
}
}
}
///
/// Translates the contract immutable store.
///
/// In the deploy code the values are written to the auxiliary heap at the predefined offset,
/// being prepared for returning to the system contract for saving.
///
/// Ignored in the runtime code.
///
pub fn store<'ctx, D>(
context: &mut Context<'ctx, D>,
index: inkwell::values::IntValue<'ctx>,
value: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
match context.code_type() {
None => {
anyhow::bail!("Immutables are not available if the contract part is undefined");
}
Some(CodeType::Deploy) => {
let index_double = context.builder().build_int_mul(
index,
context.field_const(2),
"immutable_load_index_double",
)?;
let index_offset_absolute = context.builder().build_int_add(
index_double,
context.field_const(
crate::eravm::HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA
+ (2 * era_compiler_common::BYTE_LENGTH_FIELD) as u64,
),
"index_offset_absolute",
)?;
let index_offset_pointer = Pointer::new_with_offset(
context,
AddressSpace::HeapAuxiliary,
context.field_type(),
index_offset_absolute,
"immutable_index_pointer",
);
context.build_store(index_offset_pointer, index)?;
let value_offset_absolute = context.builder().build_int_add(
index_offset_absolute,
context.field_const(era_compiler_common::BYTE_LENGTH_FIELD as u64),
"value_offset_absolute",
)?;
let value_offset_pointer = Pointer::new_with_offset(
context,
AddressSpace::HeapAuxiliary,
context.field_type(),
value_offset_absolute,
"immutable_value_pointer",
);
context.build_store(value_offset_pointer, value)?;
Ok(())
}
Some(CodeType::Runtime) => {
anyhow::bail!("Immutable writes are not available in the runtime code");
}
}
}
+98
View File
@@ -0,0 +1,98 @@
//!
//! Translates the mathematical operations.
//!
use inkwell::values::BasicValue;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Translates the `addmod` instruction.
///
pub fn add_mod<'ctx, D>(
context: &mut Context<'ctx, D>,
operand_1: inkwell::values::IntValue<'ctx>,
operand_2: inkwell::values::IntValue<'ctx>,
modulo: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context
.build_call(
context.llvm_runtime().add_mod,
&[
operand_1.as_basic_value_enum(),
operand_2.as_basic_value_enum(),
modulo.as_basic_value_enum(),
],
"add_mod_call",
)
.expect("Always exists"))
}
///
/// Translates the `mulmod` instruction.
///
pub fn mul_mod<'ctx, D>(
context: &mut Context<'ctx, D>,
operand_1: inkwell::values::IntValue<'ctx>,
operand_2: inkwell::values::IntValue<'ctx>,
modulo: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context
.build_call(
context.llvm_runtime().mul_mod,
&[
operand_1.as_basic_value_enum(),
operand_2.as_basic_value_enum(),
modulo.as_basic_value_enum(),
],
"mul_mod_call",
)
.expect("Always exists"))
}
///
/// Translates the `exp` instruction.
///
pub fn exponent<'ctx, D>(
context: &mut Context<'ctx, D>,
value: inkwell::values::IntValue<'ctx>,
exponent: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context
.build_call(
context.llvm_runtime().exp,
&[value.as_basic_value_enum(), exponent.as_basic_value_enum()],
"exp_call",
)
.expect("Always exists"))
}
///
/// Translates the `signextend` instruction.
///
pub fn sign_extend<'ctx, D>(
context: &mut Context<'ctx, D>,
bytes: inkwell::values::IntValue<'ctx>,
value: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
Ok(context
.build_call(
context.llvm_runtime().sign_extend,
&[bytes.as_basic_value_enum(), value.as_basic_value_enum()],
"sign_extend_call",
)
.expect("Always exists"))
}
@@ -0,0 +1,87 @@
//!
//! Translates the heap memory operations.
//!
use inkwell::values::BasicValue;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::pointer::Pointer;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Translates the `mload` instruction.
///
/// Uses the main heap.
///
pub fn load<'ctx, D>(
context: &mut Context<'ctx, D>,
offset: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let pointer = Pointer::new_with_offset(
context,
AddressSpace::Heap,
context.field_type(),
offset,
"memory_load_pointer",
);
context.build_load(pointer, "memory_load_result")
}
///
/// Translates the `mstore` instruction.
///
/// Uses the main heap.
///
pub fn store<'ctx, D>(
context: &mut Context<'ctx, D>,
offset: inkwell::values::IntValue<'ctx>,
value: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
let pointer = Pointer::new_with_offset(
context,
AddressSpace::Heap,
context.field_type(),
offset,
"memory_store_pointer",
);
context.build_store(pointer, value)?;
Ok(())
}
///
/// Translates the `mstore8` instruction.
///
/// Uses the main heap.
///
pub fn store_byte<'ctx, D>(
context: &mut Context<'ctx, D>,
offset: inkwell::values::IntValue<'ctx>,
value: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
let offset_pointer = Pointer::new_with_offset(
context,
AddressSpace::Heap,
context.byte_type(),
offset,
"mstore8_offset_pointer",
);
context.build_call(
context.llvm_runtime().mstore8,
&[
offset_pointer.value.as_basic_value_enum(),
value.as_basic_value_enum(),
],
"mstore8_call",
);
Ok(())
}
+21
View File
@@ -0,0 +1,21 @@
//!
//! The EVM instructions translation utils.
//!
pub mod arithmetic;
pub mod bitwise;
pub mod call;
pub mod calldata;
pub mod comparison;
pub mod context;
pub mod create;
pub mod crypto;
pub mod ether_gas;
pub mod event;
pub mod ext_code;
pub mod immutable;
pub mod math;
pub mod memory;
pub mod r#return;
pub mod return_data;
pub mod storage;
+129
View File
@@ -0,0 +1,129 @@
//!
//! Translates the transaction return operations.
//!
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::code_type::CodeType;
use crate::eravm::context::pointer::Pointer;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Translates the `return` instruction.
///
/// Unlike in EVM, zkSync constructors return the array of contract immutables.
///
pub fn r#return<'ctx, D>(
context: &mut Context<'ctx, D>,
offset: inkwell::values::IntValue<'ctx>,
length: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
match context.code_type() {
None => {
anyhow::bail!("Return is not available if the contract part is undefined");
}
Some(CodeType::Deploy) => {
let immutables_offset_pointer = Pointer::new_with_offset(
context,
AddressSpace::HeapAuxiliary,
context.field_type(),
context.field_const(crate::eravm::HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA),
"immutables_offset_pointer",
);
context.build_store(
immutables_offset_pointer,
context.field_const(era_compiler_common::BYTE_LENGTH_FIELD as u64),
)?;
let immutables_number_pointer = Pointer::new_with_offset(
context,
AddressSpace::HeapAuxiliary,
context.field_type(),
context.field_const(
crate::eravm::HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA
+ (era_compiler_common::BYTE_LENGTH_FIELD as u64),
),
"immutables_number_pointer",
);
let immutable_values_size = context.immutables_size()?;
context.build_store(
immutables_number_pointer,
context.field_const(
(immutable_values_size / era_compiler_common::BYTE_LENGTH_FIELD) as u64,
),
)?;
let immutables_size = context.builder().build_int_mul(
context.field_const(immutable_values_size as u64),
context.field_const(2),
"immutables_size",
)?;
let return_data_length = context.builder().build_int_add(
immutables_size,
context.field_const((era_compiler_common::BYTE_LENGTH_FIELD * 2) as u64),
"return_data_length",
)?;
context.build_exit(
context.integer_const(32, 0),
context.field_const(crate::eravm::HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA),
return_data_length,
)?;
}
Some(CodeType::Runtime) => {
context.build_exit(context.integer_const(32, 0), offset, length)?;
}
}
Ok(())
}
///
/// Translates the `revert` instruction.
///
pub fn revert<'ctx, D>(
context: &mut Context<'ctx, D>,
offset: inkwell::values::IntValue<'ctx>,
length: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
context.build_exit(context.integer_const(32, 1), offset, length)
}
///
/// Translates the `stop` instruction.
///
/// Is the same as `return(0, 0)`.
///
pub fn stop<D>(context: &mut Context<D>) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
r#return(
context,
context.integer_const(32, 0),
context.integer_const(32, 0),
)
}
///
/// Translates the `invalid` instruction.
///
/// Burns all gas using an out-of-bounds memory store, causing a panic.
///
pub fn invalid<D>(context: &mut Context<D>) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
crate::eravm::evm::memory::store(
context,
context.field_type().const_all_ones(),
context.field_const(0),
)?;
context.build_call(context.intrinsics().trap, &[], "invalid_trap");
Ok(())
}
@@ -0,0 +1,93 @@
//!
//! Translates the return data instructions.
//!
use inkwell::types::BasicType;
use inkwell::values::BasicValue;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::pointer::Pointer;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Translates the return data size.
///
pub fn size<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
match context.get_global_value(crate::eravm::GLOBAL_RETURN_DATA_SIZE) {
Ok(global) => Ok(global),
Err(_error) => Ok(context.field_const(0).as_basic_value_enum()),
}
}
///
/// Translates the return data copy.
///
pub fn copy<'ctx, D>(
context: &mut Context<'ctx, D>,
destination_offset: inkwell::values::IntValue<'ctx>,
source_offset: inkwell::values::IntValue<'ctx>,
size: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
let error_block = context.append_basic_block("return_data_copy_error_block");
let join_block = context.append_basic_block("return_data_copy_join_block");
let return_data_size = self::size(context)?.into_int_value();
let copy_slice_end =
context
.builder()
.build_int_add(source_offset, size, "return_data_copy_slice_end")?;
let is_copy_out_of_bounds = context.builder().build_int_compare(
inkwell::IntPredicate::UGT,
copy_slice_end,
return_data_size,
"return_data_copy_is_out_of_bounds",
)?;
context.build_conditional_branch(is_copy_out_of_bounds, error_block, join_block)?;
context.set_basic_block(error_block);
crate::eravm::evm::r#return::revert(context, context.field_const(0), context.field_const(0))?;
context.set_basic_block(join_block);
let destination = Pointer::new_with_offset(
context,
AddressSpace::Heap,
context.byte_type(),
destination_offset,
"return_data_copy_destination_pointer",
);
let return_data_pointer_global =
context.get_global(crate::eravm::GLOBAL_RETURN_DATA_POINTER)?;
let return_data_pointer_pointer = return_data_pointer_global.into();
let return_data_pointer =
context.build_load(return_data_pointer_pointer, "return_data_pointer")?;
let source = context.build_gep(
Pointer::new(
context.byte_type(),
return_data_pointer_pointer.address_space,
return_data_pointer.into_pointer_value(),
),
&[source_offset],
context.byte_type().as_basic_type_enum(),
"return_data_source_pointer",
);
context.build_memcpy(
context.intrinsics().memory_copy_from_generic,
destination,
source,
size,
"return_data_copy_memcpy_from_return_data",
)?;
Ok(())
}
@@ -0,0 +1,92 @@
//!
//! Translates the storage operations.
//!
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::pointer::Pointer;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Translates the storage load.
///
pub fn load<'ctx, D>(
context: &mut Context<'ctx, D>,
position: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let position_pointer = Pointer::new_with_offset(
context,
AddressSpace::Storage,
context.field_type(),
position,
"storage_load_position_pointer",
);
context.build_load(position_pointer, "storage_load_value")
}
///
/// Translates the storage store.
///
pub fn store<'ctx, D>(
context: &mut Context<'ctx, D>,
position: inkwell::values::IntValue<'ctx>,
value: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
let position_pointer = Pointer::new_with_offset(
context,
AddressSpace::Storage,
context.field_type(),
position,
"storage_store_position_pointer",
);
context.build_store(position_pointer, value)?;
Ok(())
}
///
/// Translates the transient storage load.
///
pub fn transient_load<'ctx, D>(
context: &mut Context<'ctx, D>,
position: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let position_pointer = Pointer::new_with_offset(
context,
AddressSpace::TransientStorage,
context.field_type(),
position,
"transient_storage_load_position_pointer",
);
context.build_load(position_pointer, "transient_storage_load_value")
}
///
/// Translates the transient storage store.
///
pub fn transient_store<'ctx, D>(
context: &mut Context<'ctx, D>,
position: inkwell::values::IntValue<'ctx>,
value: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
let position_pointer = Pointer::new_with_offset(
context,
AddressSpace::TransientStorage,
context.field_type(),
position,
"transient_storage_store_position_pointer",
);
context.build_store(position_pointer, value)?;
Ok(())
}
@@ -0,0 +1,225 @@
//!
//! Translates the ABI instructions of the EraVM Yul extension.
//!
use inkwell::types::BasicType;
use inkwell::values::BasicValue;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::pointer::Pointer;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Generates an extra ABI data getter call.
///
pub fn get_extra_abi_data<'ctx, D>(
context: &mut Context<'ctx, D>,
index: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let extra_active_data_global = context.get_global(crate::eravm::GLOBAL_EXTRA_ABI_DATA)?;
let extra_active_data_pointer = extra_active_data_global.into();
let extra_active_data_element_pointer = context.build_gep(
extra_active_data_pointer,
&[context.field_const(0), index],
context.field_type().as_basic_type_enum(),
"extra_active_data_element_pointer",
);
context.build_load(
extra_active_data_element_pointer,
"extra_active_data_element_value",
)
}
///
/// Loads the calldata pointer to the active pointer.
///
pub fn calldata_ptr_to_active<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let calldata_pointer = context.get_global_value(crate::eravm::GLOBAL_CALLDATA_POINTER)?;
context.set_global(
crate::eravm::GLOBAL_ACTIVE_POINTER,
context.byte_type().ptr_type(AddressSpace::Generic.into()),
AddressSpace::Stack,
calldata_pointer,
);
Ok(context.field_const(1).as_basic_value_enum())
}
///
/// Loads the return data pointer to the active pointer.
///
pub fn return_data_ptr_to_active<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let calldata_pointer = context.get_global_value(crate::eravm::GLOBAL_RETURN_DATA_POINTER)?;
context.set_global(
crate::eravm::GLOBAL_ACTIVE_POINTER,
context.byte_type().ptr_type(AddressSpace::Generic.into()),
AddressSpace::Stack,
calldata_pointer,
);
Ok(context.field_const(1).as_basic_value_enum())
}
///
/// Shifts the active pointer by the specified `offset`.
///
pub fn active_ptr_add_assign<'ctx, D>(
context: &mut Context<'ctx, D>,
offset: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let active_pointer = context.get_global_value(crate::eravm::GLOBAL_ACTIVE_POINTER)?;
let active_pointer_shifted = context.build_gep(
Pointer::new(
context.byte_type(),
AddressSpace::Generic,
active_pointer.into_pointer_value(),
),
&[offset],
context.byte_type().as_basic_type_enum(),
"active_pointer_shifted",
);
context.set_global(
crate::eravm::GLOBAL_ACTIVE_POINTER,
context.byte_type().ptr_type(AddressSpace::Generic.into()),
AddressSpace::Stack,
active_pointer_shifted.value,
);
Ok(context.field_const(1).as_basic_value_enum())
}
///
/// Shrinks the active pointer by the specified `offset`.
///
pub fn active_ptr_shrink_assign<'ctx, D>(
_context: &mut Context<'ctx, D>,
_offset: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
unimplemented!()
}
///
/// Writes the specified `data` into the upper 128 bits of the active pointer.
///
pub fn active_ptr_pack_assign<'ctx, D>(
_context: &mut Context<'ctx, D>,
_data: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
unimplemented!()
}
///
/// Loads a single word from the active pointer to the stack.
///
pub fn active_ptr_data_load<'ctx, D>(
context: &mut Context<'ctx, D>,
offset: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let active_pointer = context.get_global_value(crate::eravm::GLOBAL_ACTIVE_POINTER)?;
let active_pointer = context.build_gep(
Pointer::new(
context.byte_type(),
AddressSpace::Generic,
active_pointer.into_pointer_value(),
),
&[offset],
context.field_type().as_basic_type_enum(),
"active_pointer_with_offset",
);
context.build_load(active_pointer, "active_pointer_value")
}
///
/// Returns the active pointer data size.
///
pub fn active_ptr_data_size<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let active_pointer = context.get_global_value(crate::eravm::GLOBAL_ACTIVE_POINTER)?;
let active_pointer_value = context.builder().build_ptr_to_int(
active_pointer.into_pointer_value(),
context.field_type(),
"active_pointer_value",
)?;
let active_pointer_value_shifted = context.builder().build_right_shift(
active_pointer_value,
context.field_const((era_compiler_common::BIT_LENGTH_X32 * 3) as u64),
false,
"active_pointer_value_shifted",
)?;
let active_pointer_length = context.builder().build_and(
active_pointer_value_shifted,
context.field_const(u32::MAX as u64),
"active_pointer_length",
)?;
Ok(active_pointer_length.as_basic_value_enum())
}
///
/// Copies a chunk of data from the active pointer to the heap.
///
pub fn active_ptr_data_copy<'ctx, D>(
context: &mut Context<'ctx, D>,
destination_offset: inkwell::values::IntValue<'ctx>,
source_offset: inkwell::values::IntValue<'ctx>,
size: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let destination = Pointer::new_with_offset(
context,
AddressSpace::Heap,
context.byte_type(),
destination_offset,
"active_pointer_data_copy_destination_pointer",
);
let active_pointer = context.get_global_value(crate::eravm::GLOBAL_ACTIVE_POINTER)?;
let source = context.build_gep(
Pointer::new(
context.byte_type(),
AddressSpace::Generic,
active_pointer.into_pointer_value(),
),
&[source_offset],
context.byte_type().as_basic_type_enum(),
"active_pointer_data_copy_source_pointer",
);
context.build_memcpy(
context.intrinsics().memory_copy_from_generic,
destination,
source,
size,
"active_pointer_data_copy_memcpy_from_child",
)?;
Ok(context.field_const(1).as_basic_value_enum())
}
@@ -0,0 +1,302 @@
//!
//! Translates the call instructions of the EraVM Yul extension.
//!
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::function::declaration::Declaration as FunctionDeclaration;
use crate::eravm::context::pointer::Pointer;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Generates a mimic call.
///
/// The mimic call is a special type of call that can only be used in the system contracts of
/// zkSync. The call allows to call a contract with custom `msg.sender`, allowing to insert
/// system contracts as middlewares.
///
pub fn mimic<'ctx, D>(
context: &mut Context<'ctx, D>,
function: FunctionDeclaration<'ctx>,
address: inkwell::values::IntValue<'ctx>,
mimic: inkwell::values::IntValue<'ctx>,
abi_data: inkwell::values::BasicValueEnum<'ctx>,
extra_abi_data: Vec<inkwell::values::IntValue<'ctx>>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let status_code_result_pointer = context.build_alloca(
context.field_type(),
"mimic_call_result_status_code_pointer",
);
context.build_store(status_code_result_pointer, context.field_const(0))?;
let far_call_result = context
.build_call(
function,
crate::eravm::utils::external_call_arguments(
context,
abi_data,
address,
extra_abi_data,
Some(mimic),
)
.as_slice(),
"mimic_call_external",
)
.expect("IntrinsicFunction always returns a flag");
let result_abi_data = context
.builder()
.build_extract_value(
far_call_result.into_struct_value(),
0,
"mimic_call_external_result_abi_data",
)
.expect("Always exists");
let result_abi_data_pointer = Pointer::new(
context.byte_type(),
AddressSpace::Generic,
result_abi_data.into_pointer_value(),
);
let result_status_code_boolean = context
.builder()
.build_extract_value(
far_call_result.into_struct_value(),
1,
"mimic_call_external_result_status_code_boolean",
)
.expect("Always exists");
let result_status_code = context.builder().build_int_z_extend_or_bit_cast(
result_status_code_boolean.into_int_value(),
context.field_type(),
"mimic_call_external_result_status_code",
)?;
context.build_store(status_code_result_pointer, result_status_code)?;
context.write_abi_pointer(
result_abi_data_pointer,
crate::eravm::GLOBAL_RETURN_DATA_POINTER,
);
context.write_abi_data_size(
result_abi_data_pointer,
crate::eravm::GLOBAL_RETURN_DATA_SIZE,
);
context.build_load(status_code_result_pointer, "mimic_call_status_code")
}
///
/// Generates a raw far call.
///
/// Such calls can accept extra ABI arguments passed via the virtual machine registers.
///
pub fn raw_far<'ctx, D>(
_context: &mut Context<'ctx, D>,
_function: FunctionDeclaration<'ctx>,
_address: inkwell::values::IntValue<'ctx>,
_abi_data: inkwell::values::BasicValueEnum<'ctx>,
_output_offset: inkwell::values::IntValue<'ctx>,
_output_length: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
todo!();
/*
let status_code_result_pointer = context.build_alloca(
context.field_type(),
"system_far_call_result_status_code_pointer",
);
context.build_store(status_code_result_pointer, context.field_const(0));
let far_call_result = context
.build_call(
function,
crate::eravm::utils::external_call_arguments(context, abi_data, address, vec![], None)
.as_slice(),
"system_far_call_external",
)
.expect("IntrinsicFunction always returns a flag");
let result_abi_data = context
.builder()
.build_extract_value(
far_call_result.into_struct_value(),
0,
"system_far_call_external_result_abi_data",
)
.expect("Always exists");
let result_abi_data_pointer = Pointer::new(
context.byte_type(),
AddressSpace::Generic,
result_abi_data.into_pointer_value(),
);
let result_status_code_boolean = context
.builder()
.build_extract_value(
far_call_result.into_struct_value(),
1,
"system_far_call_external_result_status_code_boolean",
)
.expect("Always exists");
let result_status_code = context.builder().build_int_z_extend_or_bit_cast(
result_status_code_boolean.into_int_value(),
context.field_type(),
"system_far_call_external_result_status_code",
);
context.build_store(status_code_result_pointer, result_status_code);
let source = result_abi_data_pointer;
let destination = Pointer::new_with_offset(
context,
AddressSpace::Heap,
context.byte_type(),
output_offset,
"system_far_call_destination",
);
context.build_memcpy_return_data(
context.intrinsics().memory_copy_from_generic,
destination,
source,
output_length,
"system_far_call_memcpy_from_child",
);
context.write_abi_pointer(
result_abi_data_pointer,
crate::eravm::GLOBAL_RETURN_DATA_POINTER,
);
context.write_abi_data_size(
result_abi_data_pointer,
crate::eravm::GLOBAL_RETURN_DATA_SIZE,
);
let status_code_result =
context.build_load(status_code_result_pointer, "system_call_status_code");
Ok(status_code_result)
*/
}
///
/// Generates a system call.
///
/// Such calls can accept extra ABI arguments passed via the virtual machine registers. It is used,
/// for example, to pass the callee address and the Ether value to the `msg.value` simulator.
///
pub fn system<'ctx, D>(
context: &mut Context<'ctx, D>,
function: FunctionDeclaration<'ctx>,
address: inkwell::values::IntValue<'ctx>,
abi_data: inkwell::values::BasicValueEnum<'ctx>,
output_offset: inkwell::values::IntValue<'ctx>,
output_length: inkwell::values::IntValue<'ctx>,
extra_abi_data: Vec<inkwell::values::IntValue<'ctx>>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let status_code_result_pointer = context.build_alloca(
context.field_type(),
"system_far_call_result_status_code_pointer",
);
context.build_store(status_code_result_pointer, context.field_const(0))?;
let far_call_result = context
.build_call(
function,
crate::eravm::utils::external_call_arguments(
context,
abi_data,
address,
extra_abi_data,
None,
)
.as_slice(),
"system_far_call_external",
)
.expect("IntrinsicFunction always returns a flag");
let result_abi_data = context
.builder()
.build_extract_value(
far_call_result.into_struct_value(),
0,
"system_far_call_external_result_abi_data",
)
.expect("Always exists");
let result_abi_data_pointer = Pointer::new(
context.byte_type(),
AddressSpace::Generic,
result_abi_data.into_pointer_value(),
);
let result_status_code_boolean = context
.builder()
.build_extract_value(
far_call_result.into_struct_value(),
1,
"system_far_call_external_result_status_code_boolean",
)
.expect("Always exists");
let result_status_code = context.builder().build_int_z_extend_or_bit_cast(
result_status_code_boolean.into_int_value(),
context.field_type(),
"system_far_call_external_result_status_code",
)?;
context.build_store(status_code_result_pointer, result_status_code)?;
let source = result_abi_data_pointer;
let destination = Pointer::new_with_offset(
context,
AddressSpace::Heap,
context.byte_type(),
output_offset,
"system_far_call_destination",
);
context.build_memcpy_return_data(
context.intrinsics().memory_copy_from_generic,
destination,
source,
output_length,
"system_far_call_memcpy_from_child",
)?;
context.write_abi_pointer(
result_abi_data_pointer,
crate::eravm::GLOBAL_RETURN_DATA_POINTER,
);
context.write_abi_data_size(
result_abi_data_pointer,
crate::eravm::GLOBAL_RETURN_DATA_SIZE,
);
context.build_load(status_code_result_pointer, "system_call_status_code")
}
///
/// Checks if the instruction was called with a correct call type.
///
pub fn validate_call_type<'ctx>(
expected: FunctionDeclaration<'ctx>,
found: FunctionDeclaration<'ctx>,
instruction_name: &'static str,
) -> anyhow::Result<()> {
if expected != found {
anyhow::bail!(
"Only `{}` is allowed for the `{}` simulation, found `{}`",
expected.value.get_name().to_string_lossy(),
instruction_name,
found.value.get_name().to_string_lossy()
);
}
Ok(())
}
@@ -0,0 +1,107 @@
//!
//! Translates the const array instructions of the EraVM Yul extension.
//!
use inkwell::types::BasicType;
use inkwell::values::BasicValue;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Declares a constant array in the code section.
///
pub fn _declare<'ctx, D>(
context: &mut Context<'ctx, D>,
index: u8,
size: u16,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
context.yul_mut().const_array_declare(index, size)?;
Ok(context.field_const(1).as_basic_value_enum())
}
///
/// Sets a value in a constant array in the code section.
///
pub fn _set<'ctx, D>(
context: &mut Context<'ctx, D>,
index: u8,
offset: u16,
value: num::BigUint,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
context.yul_mut().const_array_set(index, offset, value)?;
Ok(context.field_const(1).as_basic_value_enum())
}
///
/// Finalizes a constant array in the code section, by extracting it from
/// the temporary compile-time storage, and initializing it in LLVM IR.
///
pub fn _finalize<'ctx, D>(
context: &mut Context<'ctx, D>,
index: u8,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let const_array = context.yul_mut().const_array_take(index)?;
let array_type = context.field_type().array_type(const_array.len() as u32);
let array_value = context.field_type().const_array(
const_array
.into_iter()
.map(|value| context.field_const_str_dec(value.to_string().as_str()))
.collect::<Vec<inkwell::values::IntValue<'ctx>>>()
.as_slice(),
);
context.set_global(
format!(
"{}{:03}",
crate::eravm::r#const::GLOBAL_CONST_ARRAY_PREFIX,
index
)
.as_str(),
array_type,
AddressSpace::Code,
array_value,
);
Ok(context.field_const(1).as_basic_value_enum())
}
///
/// Gets a value from a constant array in the code section.
///
pub fn _get<'ctx, D>(
context: &mut Context<'ctx, D>,
index: u8,
offset: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let identifier = format!(
"{}{:03}",
crate::eravm::r#const::GLOBAL_CONST_ARRAY_PREFIX,
index
);
let global = context.get_global(identifier.as_str())?;
let pointer = global.into();
let pointer = context.build_gep(
pointer,
&[context.field_const(0), offset],
context.field_type().as_basic_type_enum(),
format!("{}_pointer", identifier).as_str(),
);
context.build_load(pointer, format!("{}_value", identifier).as_str())
}
@@ -0,0 +1,112 @@
//!
//! Translates the general instructions of the EraVM Yul extension.
//!
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Generates a call to L1.
///
pub fn to_l1<'ctx, D>(
_context: &mut Context<'ctx, D>,
_is_first: inkwell::values::IntValue<'ctx>,
_in_0: inkwell::values::IntValue<'ctx>,
_in_1: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
unimplemented!()
}
///
/// Generates a `code source` call.
///
pub fn code_source<'ctx, D>(
_context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
todo!()
}
///
/// Generates a precompile call.
///
pub fn precompile<'ctx, D>(
_context: &mut Context<'ctx, D>,
_in_0: inkwell::values::IntValue<'ctx>,
_gas_left: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
todo!()
}
///
/// Generates a `meta` call.
///
pub fn meta<'ctx, D>(
_context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
unimplemented!()
}
///
/// Generates a `u128` context value setter call.
///
pub fn set_context_value<'ctx, D>(
_context: &mut Context<'ctx, D>,
_value: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
unimplemented!()
}
///
/// Generates a public data price setter call.
///
pub fn set_pubdata_price<'ctx, D>(
_context: &mut Context<'ctx, D>,
_value: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
unimplemented!()
}
///
/// Generates a transaction counter increment call.
///
pub fn increment_tx_counter<'ctx, D>(
_context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
unimplemented!()
}
///
/// Generates an event call.
///
pub fn event<'ctx, D>(
_context: &mut Context<'ctx, D>,
_operand_1: inkwell::values::IntValue<'ctx>,
_operand_2: inkwell::values::IntValue<'ctx>,
_is_initializer: bool,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
todo!()
}
@@ -0,0 +1,52 @@
//!
//! Translates the math instructions of the EraVM Yul extension.
//!
use inkwell::values::BasicValue;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Performs a multiplication, returning the higher register, that is the overflown part.
///
pub fn multiplication_512<'ctx, D>(
context: &mut Context<'ctx, D>,
operand_1: inkwell::values::IntValue<'ctx>,
operand_2: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let operand_1_extended = context.builder().build_int_z_extend_or_bit_cast(
operand_1,
context.integer_type(era_compiler_common::BIT_LENGTH_FIELD * 2),
"multiplication_512_operand_1_extended",
)?;
let operand_2_extended = context.builder().build_int_z_extend_or_bit_cast(
operand_2,
context.integer_type(era_compiler_common::BIT_LENGTH_FIELD * 2),
"multiplication_512_operand_2_extended",
)?;
let result_extended = context.builder().build_int_mul(
operand_1_extended,
operand_2_extended,
"multiplication_512_result_extended",
)?;
let result_shifted = context.builder().build_right_shift(
result_extended,
context.integer_const(
era_compiler_common::BIT_LENGTH_FIELD * 2,
era_compiler_common::BIT_LENGTH_FIELD as u64,
),
false,
"multiplication_512_result_shifted",
)?;
let result = context.builder().build_int_truncate_or_bit_cast(
result_shifted,
context.field_type(),
"multiplication_512_result",
)?;
Ok(result.as_basic_value_enum())
}
@@ -0,0 +1,9 @@
//!
//! The EraVM instructions translation utils.
//!
pub mod abi;
pub mod call;
pub mod const_array;
pub mod general;
pub mod math;
@@ -0,0 +1,33 @@
//!
//! The metadata hash mode.
//!
use std::str::FromStr;
use serde::Deserialize;
use serde::Serialize;
///
/// The metadata hash mode.
///
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MetadataHash {
/// Do not include bytecode hash.
#[serde(rename = "none")]
None,
/// The default keccak256 hash.
#[serde(rename = "keccak256")]
Keccak256,
}
impl FromStr for MetadataHash {
type Err = anyhow::Error;
fn from_str(string: &str) -> Result<Self, Self::Err> {
match string {
"none" => Ok(Self::None),
"keccak256" => Ok(Self::Keccak256),
_ => anyhow::bail!("Unknown bytecode hash mode: `{}`", string),
}
}
}
+189
View File
@@ -0,0 +1,189 @@
//!
//! The LLVM context library.
//!
pub mod r#const;
pub mod context;
pub mod evm;
pub mod extensions;
pub mod metadata_hash;
pub mod utils;
pub use self::r#const::*;
use crate::debug_config::DebugConfig;
use crate::optimizer::settings::Settings as OptimizerSettings;
use self::context::build::Build;
use self::context::Context;
///
/// Initializes the EraVM target machine.
///
pub fn initialize_target() {
inkwell::targets::Target::initialize_riscv(&Default::default());
}
///
/// Builds EraVM assembly text.
///
pub fn build_assembly_text(
contract_path: &str,
assembly_text: &str,
_metadata_hash: Option<[u8; era_compiler_common::BYTE_LENGTH_FIELD]>,
debug_config: Option<&DebugConfig>,
) -> anyhow::Result<Build> {
if let Some(debug_config) = debug_config {
debug_config.dump_assembly(contract_path, assembly_text)?;
}
/*
let mut assembly =
zkevm_assembly::Assembly::from_string(assembly_text.to_owned(), metadata_hash).map_err(
|error| {
anyhow::anyhow!(
"The contract `{}` assembly parsing error: {}",
contract_path,
error,
)
},
)?;
let bytecode_words = match zkevm_assembly::get_encoding_mode() {
zkevm_assembly::RunningVmEncodingMode::Production => { assembly.compile_to_bytecode_for_mode::<8, zkevm_opcode_defs::decoding::EncodingModeProduction>() },
zkevm_assembly::RunningVmEncodingMode::Testing => { assembly.compile_to_bytecode_for_mode::<16, zkevm_opcode_defs::decoding::EncodingModeTesting>() },
}
.map_err(|error| {
anyhow::anyhow!(
"The contract `{}` assembly-to-bytecode conversion error: {}",
contract_path,
error,
)
})?;
let bytecode_hash = match zkevm_assembly::get_encoding_mode() {
zkevm_assembly::RunningVmEncodingMode::Production => {
zkevm_opcode_defs::utils::bytecode_to_code_hash_for_mode::<
8,
zkevm_opcode_defs::decoding::EncodingModeProduction,
>(bytecode_words.as_slice())
}
zkevm_assembly::RunningVmEncodingMode::Testing => {
zkevm_opcode_defs::utils::bytecode_to_code_hash_for_mode::<
16,
zkevm_opcode_defs::decoding::EncodingModeTesting,
>(bytecode_words.as_slice())
}
}
.map(hex::encode)
.map_err(|_error| {
anyhow::anyhow!("The contract `{}` bytecode hashing error", contract_path,)
})?;
let bytecode = bytecode_words.into_iter().flatten().collect();
*/
Ok(Build::new(
assembly_text.to_owned(),
Default::default(),
hex::decode(assembly_text).unwrap(),
Default::default(),
))
}
///
/// Implemented by items which are translated into LLVM IR.
///
#[allow(clippy::upper_case_acronyms)]
pub trait WriteLLVM<D>
where
D: Dependency + Clone,
{
///
/// Declares the entity in the LLVM IR.
/// Is usually performed in order to use the item before defining it.
///
fn declare(&mut self, _context: &mut Context<D>) -> anyhow::Result<()> {
Ok(())
}
///
/// Translates the entity into LLVM IR.
///
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()>;
}
///
/// The dummy LLVM writable entity.
///
#[derive(Debug, Default, Clone)]
pub struct DummyLLVMWritable {}
impl<D> WriteLLVM<D> for DummyLLVMWritable
where
D: Dependency + Clone,
{
fn into_llvm(self, _context: &mut Context<D>) -> anyhow::Result<()> {
Ok(())
}
}
///
/// Implemented by items managing project dependencies.
///
pub trait Dependency {
///
/// Compiles a project dependency.
///
fn compile(
dependency: Self,
path: &str,
optimizer_settings: OptimizerSettings,
is_system_mode: bool,
include_metadata_hash: bool,
debug_config: Option<DebugConfig>,
) -> anyhow::Result<String>;
///
/// Resolves a full contract path.
///
fn resolve_path(&self, identifier: &str) -> anyhow::Result<String>;
///
/// Resolves a library address.
///
fn resolve_library(&self, path: &str) -> anyhow::Result<String>;
}
///
/// The dummy dependency entity.
///
#[derive(Debug, Default, Clone)]
pub struct DummyDependency {}
impl Dependency for DummyDependency {
fn compile(
_dependency: Self,
_path: &str,
_optimizer_settings: OptimizerSettings,
_is_system_mode: bool,
_include_metadata_hash: bool,
_debug_config: Option<DebugConfig>,
) -> anyhow::Result<String> {
Ok(String::new())
}
///
/// Resolves a full contract path.
///
fn resolve_path(&self, _identifier: &str) -> anyhow::Result<String> {
Ok(String::new())
}
///
/// Resolves a library address.
///
fn resolve_library(&self, _path: &str) -> anyhow::Result<String> {
Ok(String::new())
}
}
+235
View File
@@ -0,0 +1,235 @@
//!
//! Some LLVM IR generator utilies.
//!
use inkwell::values::BasicValue;
use crate::eravm::context::address_space::AddressSpace;
use crate::eravm::context::function::llvm_runtime::LLVMRuntime;
use crate::eravm::context::Context;
use crate::eravm::Dependency;
///
/// Clamps `value` to `max_value`, if `value` is bigger than `max_value`.
///
pub fn clamp<'ctx, D>(
context: &mut Context<'ctx, D>,
value: inkwell::values::IntValue<'ctx>,
max_value: inkwell::values::IntValue<'ctx>,
name: &str,
) -> anyhow::Result<inkwell::values::IntValue<'ctx>>
where
D: Dependency + Clone,
{
let in_bounds_block = context.append_basic_block(format!("{name}_is_bounds_block").as_str());
let join_block = context.append_basic_block(format!("{name}_join_block").as_str());
let pointer = context.build_alloca(context.field_type(), format!("{name}_pointer").as_str());
context.build_store(pointer, max_value)?;
let is_in_bounds = context.builder().build_int_compare(
inkwell::IntPredicate::ULE,
value,
max_value,
format!("{name}_is_in_bounds").as_str(),
)?;
context.build_conditional_branch(is_in_bounds, in_bounds_block, join_block)?;
context.set_basic_block(in_bounds_block);
context.build_store(pointer, value)?;
context.build_unconditional_branch(join_block);
context.set_basic_block(join_block);
let result = context.build_load(pointer, name)?;
Ok(result.into_int_value())
}
///
/// Generates an exception.
///
pub fn throw<D>(context: &Context<D>)
where
D: Dependency + Clone,
{
context.build_call(
context.llvm_runtime().cxa_throw,
&[context
.byte_type()
.ptr_type(AddressSpace::Stack.into())
.get_undef()
.as_basic_value_enum(); 3],
LLVMRuntime::FUNCTION_CXA_THROW,
);
context.build_unreachable();
}
///
/// Returns the full list of arguments for an external call.
///
/// Performs the extra ABI data padding and adds the mimic call extra argument.
///
pub fn external_call_arguments<'ctx, D>(
_context: &Context<'ctx, D>,
abi_data: inkwell::values::BasicValueEnum<'ctx>,
address: inkwell::values::IntValue<'ctx>,
_extra_abi_data: Vec<inkwell::values::IntValue<'ctx>>,
mimic: Option<inkwell::values::IntValue<'ctx>>,
) -> Vec<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let mut result = Vec::with_capacity(
crate::eravm::context::function::runtime::entry::Entry::MANDATORY_ARGUMENTS_COUNT
+ crate::eravm::EXTRA_ABI_DATA_SIZE
+ usize::from(mimic.is_some()),
);
result.push(abi_data);
result.push(address.as_basic_value_enum());
//result.extend(
// pad_extra_abi_data(context, extra_abi_data)
// .into_iter()
// .map(|value| value.as_basic_value_enum()),
//);
if let Some(mimic) = mimic {
result.push(mimic.as_basic_value_enum());
}
result
}
///
/// Generates an ABI data for an external call.
///
/// If `gas` is `None`, it is fetched from the contract context.
///
pub fn abi_data<'ctx, D>(
context: &mut Context<'ctx, D>,
input_offset: inkwell::values::IntValue<'ctx>,
input_length: inkwell::values::IntValue<'ctx>,
gas: Option<inkwell::values::IntValue<'ctx>>,
address_space: AddressSpace,
is_system_call: bool,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let input_offset = crate::eravm::utils::clamp(
context,
input_offset,
context.field_const(u32::MAX as u64),
"abi_data_input_offset",
)?;
let input_length = crate::eravm::utils::clamp(
context,
input_length,
context.field_const(u32::MAX as u64),
"abi_data_input_length",
)?;
let gas = match gas {
Some(gas) => gas,
None => crate::eravm::evm::ether_gas::gas(context)?.into_int_value(),
};
let gas = crate::eravm::utils::clamp(
context,
gas,
context.field_const(u32::MAX as u64),
"abi_data_gas",
)?;
let input_offset_shifted = context.builder().build_left_shift(
input_offset,
context.field_const((era_compiler_common::BIT_LENGTH_X32 * 2) as u64),
"abi_data_input_offset_shifted",
)?;
let input_length_shifted = context.builder().build_left_shift(
input_length,
context.field_const((era_compiler_common::BIT_LENGTH_X32 * 3) as u64),
"abi_data_input_length_shifted",
)?;
let gas_shifted = context.builder().build_left_shift(
gas,
context.field_const((era_compiler_common::BIT_LENGTH_X32 * 6) as u64),
"abi_data_gas_shifted",
)?;
let mut abi_data = context.builder().build_int_add(
input_offset_shifted,
input_length_shifted,
"abi_data_offset_and_length",
)?;
abi_data = context
.builder()
.build_int_add(abi_data, gas_shifted, "abi_data_add_gas")?;
if let AddressSpace::HeapAuxiliary = address_space {
let auxiliary_heap_marker_shifted = context.builder().build_left_shift(
context.field_const(zkevm_opcode_defs::FarCallForwardPageType::UseAuxHeap as u64),
context.field_const((era_compiler_common::BIT_LENGTH_X32 * 7) as u64),
"abi_data_auxiliary_heap_marker_shifted",
)?;
abi_data = context.builder().build_int_add(
abi_data,
auxiliary_heap_marker_shifted,
"abi_data_add_heap_auxiliary_marker",
)?;
}
if is_system_call {
let auxiliary_heap_marker_shifted = context.builder().build_left_shift(
context.field_const(zkevm_opcode_defs::FarCallForwardPageType::UseAuxHeap as u64),
context.field_const(
((era_compiler_common::BIT_LENGTH_X32 * 7)
+ (era_compiler_common::BIT_LENGTH_BYTE * 3)) as u64,
),
"abi_data_system_call_marker_shifted",
)?;
abi_data = context.builder().build_int_add(
abi_data,
auxiliary_heap_marker_shifted,
"abi_data_add_system_call_marker",
)?;
}
Ok(abi_data.as_basic_value_enum())
}
///
/// Pads the extra ABI data with `i256::undef`, so it always consists of 10 values.
///
pub fn pad_extra_abi_data<'ctx, D>(
context: &Context<'ctx, D>,
initial_data: Vec<inkwell::values::IntValue<'ctx>>,
) -> [inkwell::values::IntValue<'ctx>; crate::eravm::EXTRA_ABI_DATA_SIZE]
where
D: Dependency + Clone,
{
let mut padded_data = initial_data;
padded_data.extend(vec![
context.field_undef();
crate::eravm::EXTRA_ABI_DATA_SIZE - padded_data.len()
]);
padded_data.try_into().expect("Always valid")
}
///
/// Computes the `keccak256` hash for `preimage`.
///
pub fn keccak256(preimage: &[u8]) -> String {
use sha3::Digest;
let hash_bytes = sha3::Keccak256::digest(preimage);
hash_bytes
.into_iter()
.map(|byte| format!("{byte:02x}"))
.collect::<Vec<String>>()
.join("")
}
#[cfg(test)]
mod tests {
#[test]
fn keccak256() {
assert_eq!(
super::keccak256("zksync".as_bytes()),
"0238fb1ab06c28c32885f9a4842207ac480c2467df26b6c58e201679628c5a5b"
);
}
}
+82
View File
@@ -0,0 +1,82 @@
//!
//! The LLVM context library.
//!
pub(crate) mod debug_config;
pub(crate) mod eravm;
pub(crate) mod optimizer;
pub(crate) mod target_machine;
pub use self::debug_config::ir_type::IRType as DebugConfigIR;
pub use self::debug_config::DebugConfig;
pub use self::eravm::build_assembly_text as eravm_build_assembly_text;
pub use self::eravm::context::address_space::AddressSpace as EraVMAddressSpace;
pub use self::eravm::context::argument::Argument as EraVMArgument;
pub use self::eravm::context::attribute::Attribute as EraVMAttribute;
pub use self::eravm::context::build::Build as EraVMBuild;
pub use self::eravm::context::code_type::CodeType as EraVMCodeType;
pub use self::eravm::context::evmla_data::EVMLAData as EraVMContextEVMLAData;
pub use self::eravm::context::function::block::evmla_data::key::Key as EraVMFunctionBlockKey;
pub use self::eravm::context::function::block::evmla_data::EVMLAData as EraVMFunctionBlockEVMLAData;
pub use self::eravm::context::function::block::Block as EraVMFunctionBlock;
pub use self::eravm::context::function::declaration::Declaration as EraVMFunctionDeclaration;
pub use self::eravm::context::function::evmla_data::EVMLAData as EraVMFunctionEVMLAData;
pub use self::eravm::context::function::intrinsics::Intrinsics as EraVMIntrinsicFunction;
pub use self::eravm::context::function::llvm_runtime::LLVMRuntime as EraVMLLVMRuntime;
pub use self::eravm::context::function::r#return::Return as EraVMFunctionReturn;
pub use self::eravm::context::function::runtime::deploy_code::DeployCode as EraVMDeployCodeFunction;
pub use self::eravm::context::function::runtime::entry::Entry as EraVMEntryFunction;
pub use self::eravm::context::function::runtime::runtime_code::RuntimeCode as EraVMRuntimeCodeFunction;
pub use self::eravm::context::function::runtime::Runtime as EraVMRuntime;
pub use self::eravm::context::function::vyper_data::VyperData as EraVMFunctionVyperData;
pub use self::eravm::context::function::yul_data::YulData as EraVMFunctionYulData;
pub use self::eravm::context::function::Function as EraVMFunction;
pub use self::eravm::context::global::Global as EraVMGlobal;
pub use self::eravm::context::pointer::Pointer as EraVMPointer;
pub use self::eravm::context::r#loop::Loop as EraVMLoop;
pub use self::eravm::context::solidity_data::SolidityData as EraVMContextSolidityData;
pub use self::eravm::context::vyper_data::VyperData as EraVMContextVyperData;
pub use self::eravm::context::yul_data::YulData as EraVMContextYulData;
pub use self::eravm::context::Context as EraVMContext;
pub use self::eravm::evm::arithmetic as eravm_evm_arithmetic;
pub use self::eravm::evm::bitwise as eravm_evm_bitwise;
pub use self::eravm::evm::call as eravm_evm_call;
pub use self::eravm::evm::calldata as eravm_evm_calldata;
pub use self::eravm::evm::comparison as eravm_evm_comparison;
pub use self::eravm::evm::context as eravm_evm_contract_context;
pub use self::eravm::evm::create as eravm_evm_create;
pub use self::eravm::evm::crypto as eravm_evm_crypto;
pub use self::eravm::evm::ether_gas as eravm_evm_ether_gas;
pub use self::eravm::evm::event as eravm_evm_event;
pub use self::eravm::evm::ext_code as eravm_evm_ext_code;
pub use self::eravm::evm::immutable as eravm_evm_immutable;
pub use self::eravm::evm::math as eravm_evm_math;
pub use self::eravm::evm::memory as eravm_evm_memory;
pub use self::eravm::evm::r#return as eravm_evm_return;
pub use self::eravm::evm::return_data as eravm_evm_return_data;
pub use self::eravm::evm::storage as eravm_evm_storage;
pub use self::eravm::extensions::abi as eravm_abi;
pub use self::eravm::extensions::call as eravm_call;
pub use self::eravm::extensions::general as eravm_general;
pub use self::eravm::extensions::math as eravm_math;
pub use self::eravm::metadata_hash::MetadataHash as EraVMMetadataHash;
pub use self::eravm::r#const as eravm_const;
pub use self::eravm::utils as eravm_utils;
pub use self::eravm::Dependency as EraVMDependency;
pub use self::eravm::DummyDependency as EraVMDummyDependency;
pub use self::eravm::DummyLLVMWritable as EraVMDummyLLVMWritable;
pub use self::eravm::WriteLLVM as EraVMWriteLLVM;
pub use self::optimizer::settings::size_level::SizeLevel as OptimizerSettingsSizeLevel;
pub use self::optimizer::settings::Settings as OptimizerSettings;
pub use self::optimizer::Optimizer;
pub use self::target_machine::target::Target;
pub use self::target_machine::TargetMachine;
///
/// Initializes the target machine.
///
pub fn initialize_target(target: Target) {
match target {
Target::PVM => self::eravm::initialize_target(),
}
}
+51
View File
@@ -0,0 +1,51 @@
//!
//! The LLVM optimizing tools.
//!
pub mod settings;
use serde::Deserialize;
use serde::Serialize;
use crate::target_machine::TargetMachine;
use self::settings::Settings;
///
/// The LLVM optimizing tools.
///
#[derive(Debug, Serialize, Deserialize)]
pub struct Optimizer {
/// The optimizer settings.
settings: Settings,
}
impl Optimizer {
///
/// A shortcut constructor.
///
pub fn new(settings: Settings) -> Self {
Self { settings }
}
///
/// Runs the new pass manager.
///
pub fn run(
&self,
target_machine: &TargetMachine,
module: &inkwell::module::Module,
) -> Result<(), inkwell::support::LLVMString> {
target_machine.run_optimization_passes(
module,
format!("default<O{}>", self.settings.middle_end_as_string()).as_str(),
)
}
///
/// Returns the optimizer settings reference.
///
pub fn settings(&self) -> &Settings {
&self.settings
}
}
@@ -0,0 +1,273 @@
//!
//! The LLVM optimizer settings.
//!
pub mod size_level;
use serde::Deserialize;
use serde::Serialize;
use itertools::Itertools;
use self::size_level::SizeLevel;
///
/// The LLVM optimizer settings.
///
#[derive(Debug, Serialize, Deserialize, Clone, Eq)]
pub struct Settings {
/// The middle-end optimization level.
pub level_middle_end: inkwell::OptimizationLevel,
/// The middle-end size optimization level.
pub level_middle_end_size: SizeLevel,
/// The back-end optimization level.
pub level_back_end: inkwell::OptimizationLevel,
/// Fallback to optimizing for size if the bytecode is too large.
pub is_fallback_to_size_enabled: bool,
/// Whether the system request memoization is disabled.
pub is_system_request_memoization_disabled: bool,
/// Whether the LLVM `verify each` option is enabled.
pub is_verify_each_enabled: bool,
/// Whether the LLVM `debug logging` option is enabled.
pub is_debug_logging_enabled: bool,
}
impl Settings {
///
/// A shortcut constructor.
///
pub fn new(
level_middle_end: inkwell::OptimizationLevel,
level_middle_end_size: SizeLevel,
level_back_end: inkwell::OptimizationLevel,
) -> Self {
Self {
level_middle_end,
level_middle_end_size,
level_back_end,
is_fallback_to_size_enabled: false,
is_system_request_memoization_disabled: false,
is_verify_each_enabled: false,
is_debug_logging_enabled: false,
}
}
///
/// A shortcut constructor with debugging tools.
///
pub fn new_debug(
level_middle_end: inkwell::OptimizationLevel,
level_middle_end_size: SizeLevel,
level_back_end: inkwell::OptimizationLevel,
is_verify_each_enabled: bool,
is_debug_logging_enabled: bool,
) -> Self {
Self {
level_middle_end,
level_middle_end_size,
level_back_end,
is_fallback_to_size_enabled: false,
is_system_request_memoization_disabled: false,
is_verify_each_enabled,
is_debug_logging_enabled,
}
}
///
/// Creates settings from a CLI optimization parameter.
///
pub fn try_from_cli(value: char) -> anyhow::Result<Self> {
Ok(match value {
'0' => Self::new(
// The middle-end optimization level.
inkwell::OptimizationLevel::None,
// The middle-end size optimization level.
SizeLevel::Zero,
// The back-end optimization level.
inkwell::OptimizationLevel::None,
),
'1' => Self::new(
inkwell::OptimizationLevel::Less,
SizeLevel::Zero,
// The back-end does not currently distinguish between O1, O2, and O3.
inkwell::OptimizationLevel::Less,
),
'2' => Self::new(
inkwell::OptimizationLevel::Default,
SizeLevel::Zero,
// The back-end does not currently distinguish between O1, O2, and O3.
inkwell::OptimizationLevel::Default,
),
'3' => Self::new(
inkwell::OptimizationLevel::Aggressive,
SizeLevel::Zero,
inkwell::OptimizationLevel::Aggressive,
),
's' => Self::new(
// The middle-end optimization level is ignored when SizeLevel is set.
inkwell::OptimizationLevel::Default,
SizeLevel::S,
inkwell::OptimizationLevel::Aggressive,
),
'z' => Self::new(
// The middle-end optimization level is ignored when SizeLevel is set.
inkwell::OptimizationLevel::Default,
SizeLevel::Z,
inkwell::OptimizationLevel::Aggressive,
),
char => anyhow::bail!("Unexpected optimization option '{}'", char),
})
}
///
/// Returns the settings without optimizations.
///
pub fn none() -> Self {
Self::new(
inkwell::OptimizationLevel::None,
SizeLevel::Zero,
inkwell::OptimizationLevel::None,
)
}
///
/// Returns the settings for the optimal number of VM execution cycles.
///
pub fn cycles() -> Self {
Self::new(
inkwell::OptimizationLevel::Aggressive,
SizeLevel::Zero,
inkwell::OptimizationLevel::Aggressive,
)
}
///
/// Returns the settings for the optimal size.
///
pub fn size() -> Self {
Self::new(
inkwell::OptimizationLevel::Default,
SizeLevel::Z,
inkwell::OptimizationLevel::Aggressive,
)
}
///
/// Returns the middle-end optimization parameter as string.
///
pub fn middle_end_as_string(&self) -> String {
match self.level_middle_end_size {
SizeLevel::Zero => (self.level_middle_end as u8).to_string(),
size_level => size_level.to_string(),
}
}
///
/// Checks whether there are middle-end optimizations enabled.
///
pub fn is_middle_end_enabled(&self) -> bool {
self.level_middle_end != inkwell::OptimizationLevel::None
|| self.level_middle_end_size != SizeLevel::Zero
}
///
/// Returns all possible combinations of the optimizer settings.
///
/// Used only for testing purposes.
///
pub fn combinations() -> Vec<Self> {
let performance_combinations: Vec<Self> = vec![
inkwell::OptimizationLevel::None,
inkwell::OptimizationLevel::Less,
inkwell::OptimizationLevel::Default,
inkwell::OptimizationLevel::Aggressive,
]
.into_iter()
.cartesian_product(vec![
inkwell::OptimizationLevel::None,
inkwell::OptimizationLevel::Aggressive,
])
.map(|(optimization_level_middle, optimization_level_back)| {
Self::new(
optimization_level_middle,
SizeLevel::Zero,
optimization_level_back,
)
})
.collect();
let size_combinations: Vec<Self> = vec![SizeLevel::S, SizeLevel::Z]
.into_iter()
.cartesian_product(vec![
inkwell::OptimizationLevel::None,
inkwell::OptimizationLevel::Aggressive,
])
.map(|(size_level, optimization_level_back)| {
Self::new(
inkwell::OptimizationLevel::Default,
size_level,
optimization_level_back,
)
})
.collect();
let mut combinations = performance_combinations;
combinations.extend(size_combinations);
combinations
}
///
/// Sets the fallback to optimizing for size if the bytecode is too large.
///
pub fn enable_fallback_to_size(&mut self) {
self.is_fallback_to_size_enabled = true;
}
///
/// Disables the system request memoization.
///
pub fn disable_system_request_memoization(&mut self) {
self.is_system_request_memoization_disabled = true;
}
///
/// Whether the fallback to optimizing for size is enabled.
///
pub fn is_fallback_to_size_enabled(&self) -> bool {
self.is_fallback_to_size_enabled
}
///
/// Whether the system request memoization is disabled.
///
pub fn is_system_request_memoization_disabled(&self) -> bool {
self.is_system_request_memoization_disabled
}
}
impl PartialEq for Settings {
fn eq(&self, other: &Self) -> bool {
self.level_middle_end == other.level_middle_end
&& self.level_middle_end_size == other.level_middle_end_size
&& self.level_back_end == other.level_back_end
}
}
impl std::fmt::Display for Settings {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"M{}B{}",
self.middle_end_as_string(),
self.level_back_end as u8,
)
}
}
@@ -0,0 +1,39 @@
//!
//! The LLVM optimizer settings size level.
//!
use serde::Deserialize;
use serde::Serialize;
///
/// The LLVM optimizer settings size level.
///
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
pub enum SizeLevel {
/// No size optimizations.
Zero,
/// The default size optimizations.
S,
/// The aggresize size optimizations.
Z,
}
impl From<SizeLevel> for u32 {
fn from(level: SizeLevel) -> Self {
match level {
SizeLevel::Zero => 0,
SizeLevel::S => 1,
SizeLevel::Z => 2,
}
}
}
impl std::fmt::Display for SizeLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SizeLevel::Zero => write!(f, "0"),
SizeLevel::S => write!(f, "s"),
SizeLevel::Z => write!(f, "z"),
}
}
}

Some files were not shown because too many files have changed in this diff Show More