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
@@ -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"),
}
}
}
@@ -0,0 +1,126 @@
//!
//! The LLVM target machine.
//!
pub mod target;
use crate::optimizer::settings::size_level::SizeLevel as OptimizerSettingsSizeLevel;
use crate::optimizer::settings::Settings as OptimizerSettings;
use self::target::Target;
///
/// The LLVM target machine.
///
#[derive(Debug)]
pub struct TargetMachine {
/// The LLVM target.
target: Target,
/// The LLVM target machine reference.
target_machine: inkwell::targets::TargetMachine,
/// The optimizer settings.
optimizer_settings: OptimizerSettings,
}
impl TargetMachine {
/// The LLVM target name.
pub const VM_TARGET_NAME: &'static str = "riscv32";
/// The LLVM target triple.
pub const VM_TARGET_TRIPLE: &'static str = "riscv32-unknown-unknown-elf";
///
/// A shortcut constructor.
///
/// A separate instance for every optimization level is created.
///
pub fn new(target: Target, optimizer_settings: &OptimizerSettings) -> anyhow::Result<Self> {
let target_machine = inkwell::targets::Target::from_name(target.name())
.ok_or_else(|| anyhow::anyhow!("LLVM target machine `{}` not found", target.name()))?
.create_target_machine(
&inkwell::targets::TargetTriple::create(target.triple()),
"generic-rv32",
"+e,+m",
optimizer_settings.level_back_end,
inkwell::targets::RelocMode::PIC,
inkwell::targets::CodeModel::Default,
)
.ok_or_else(|| {
anyhow::anyhow!(
"LLVM target machine `{}` initialization error",
target.name(),
)
})?;
Ok(Self {
target,
target_machine,
optimizer_settings: optimizer_settings.to_owned(),
})
}
///
/// Sets the target-specific data in the module.
///
pub fn set_target_data(&self, module: &inkwell::module::Module) {
module.set_triple(&self.target_machine.get_triple());
let data_layout = self
.target_machine
.get_target_data()
.get_data_layout()
.as_str()
.to_str()
.expect("datalayout sting should be valid")
.to_owned();
module.set_data_layout(&self.target_machine.get_target_data().get_data_layout());
}
///
/// Writes the LLVM module to a memory buffer.
///
pub fn write_to_memory_buffer(
&self,
module: &inkwell::module::Module,
) -> Result<inkwell::memory_buffer::MemoryBuffer, inkwell::support::LLVMString> {
match self.target {
Target::PVM => self
.target_machine
.write_to_memory_buffer(module, inkwell::targets::FileType::Object),
}
}
///
/// Runs the optimization passes on `module`.
///
pub fn run_optimization_passes(
&self,
module: &inkwell::module::Module,
passes: &str,
) -> Result<(), inkwell::support::LLVMString> {
let pass_builder_options = inkwell::passes::PassBuilderOptions::create();
pass_builder_options.set_verify_each(self.optimizer_settings.is_verify_each_enabled);
pass_builder_options.set_debug_logging(self.optimizer_settings.is_debug_logging_enabled);
pass_builder_options.set_loop_unrolling(
self.optimizer_settings.level_middle_end_size == OptimizerSettingsSizeLevel::Zero,
);
pass_builder_options.set_merge_functions(true);
module.run_passes(passes, &self.target_machine, pass_builder_options)
}
///
/// Returns the target triple.
///
pub fn get_triple(&self) -> inkwell::targets::TargetTriple {
self.target_machine.get_triple()
}
///
/// Returns the target data.
///
pub fn get_target_data(&self) -> inkwell::targets::TargetData {
self.target_machine.get_target_data()
}
}
@@ -0,0 +1,66 @@
//!
//! The LLVM target.
//!
use std::str::FromStr;
///
/// The LLVM target.
///
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Target {
/// The EraVM target.
PVM,
}
impl Target {
///
/// Returns the target name.
///
pub fn name(&self) -> &str {
match self {
Self::PVM => "riscv32",
}
}
///
/// Returns the target triple.
///
pub fn triple(&self) -> &str {
match self {
Self::PVM => "riscv32-unknown-unknown-elf",
}
}
///
/// Returns the target production name.
///
pub fn production_name(&self) -> &str {
match self {
Self::PVM => "PVM",
}
}
}
impl FromStr for Target {
type Err = anyhow::Error;
fn from_str(string: &str) -> Result<Self, Self::Err> {
match string {
"riscv32" => Ok(Self::PVM),
_ => Err(anyhow::anyhow!(
"Unknown target `{}`. Supported targets: {:?}",
string,
Self::PVM
)),
}
}
}
impl std::fmt::Display for Target {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Target::PVM => write!(f, "riscv32"),
}
}
}