rename target to polkavm

Signed-off-by: xermicus <cyrill@parity.io>
This commit is contained in:
xermicus
2024-05-02 08:47:44 +02:00
parent 9fc24af355
commit 336fc63f1d
112 changed files with 876 additions and 873 deletions
+71
View File
@@ -0,0 +1,71 @@
//! The LLVM context constants.
/// The LLVM framework version.
pub const LLVM_VERSION: semver::Version = semver::Version::new(18, 1, 4);
/// The PolkaVM version.
pub const ZKEVM_VERSION: semver::Version = semver::Version::new(1, 3, 2);
/// The register width sized type
pub static XLEN: usize = revive_common::BIT_LENGTH_X32;
/// 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 * (revive_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 =
revive_common::BYTE_LENGTH_X32 + (revive_common::BYTE_LENGTH_FIELD * 4);
@@ -0,0 +1,35 @@
//! The address space aliases.
/// The address space aliases.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AddressSpace {
/// The stack memory.
#[default]
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,64 @@
//! 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,124 @@
//! 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 `Attributes.inc` file
/// inside of the LLVM build directory. This order is actually generated during the building.
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Attribute {
Unused = 0,
AllocAlign = 1,
AllocatedPointer = 2,
AlwaysInline = 3,
Builtin = 4,
Cold = 5,
Convergent = 6,
CoroDestroyOnlyWhenComplete = 7,
DeadOnUnwind = 8,
DisableSanitizerInstrumentation = 9,
FnRetThunkExtern = 10,
Hot = 11,
ImmArg = 12,
InReg = 13,
InlineHint = 14,
JumpTable = 15,
MinSize = 16,
MustProgress = 17,
Naked = 18,
Nest = 19,
NoAlias = 20,
NoBuiltin = 21,
NoCallback = 22,
NoCapture = 23,
NoCfCheck = 24,
NoDuplicate = 25,
NoFree = 26,
NoImplicitFloat = 27,
NoInline = 28,
NoMerge = 29,
NoProfile = 30,
NoRecurse = 31,
NoRedZone = 32,
NoReturn = 33,
NoSanitizeBounds = 34,
NoSanitizeCoverage = 35,
NoSync = 36,
NoUndef = 37,
NoUnwind = 38,
NonLazyBind = 39,
NonNull = 40,
NullPointerIsValid = 41,
OptForFuzzing = 42,
OptimizeForDebugging = 43,
OptimizeForSize = 44,
OptimizeNone = 45,
PresplitCoroutine = 46,
ReadNone = 47,
ReadOnly = 48,
Returned = 49,
ReturnsTwice = 50,
SExt = 51,
SafeStack = 52,
SanitizeAddress = 53,
SanitizeHWAddress = 54,
SanitizeMemTag = 55,
SanitizeMemory = 56,
SanitizeThread = 57,
ShadowCallStack = 58,
SkipProfile = 59,
Speculatable = 60,
SpeculativeLoadHardening = 61,
StackProtect = 62,
StackProtectReq = 63,
StackProtectStrong = 64,
StrictFP = 65,
SwiftAsync = 66,
SwiftError = 67,
SwiftSelf = 68,
WillReturn = 69,
Writable = 70,
WriteOnly = 71,
ZExt = 72,
// LastEnumAttr = 72,
// FirstTypeAttr = 73,
ByRef = 73,
ByVal = 74,
ElementType = 75,
InAlloca = 76,
Preallocated = 77,
StructRet = 78,
// LastTypeAttr = 78,
// FirstIntAttr = 79,
Alignment = 79,
AllocKind = 80,
AllocSize = 81,
Dereferenceable = 82,
DereferenceableOrNull = 83,
Memory = 84,
NoFPClass = 85,
StackAlignment = 86,
UWTable = 87,
VScaleRange = 88,
// LastIntAttr = 88,
}
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),
"NoReturn" => Ok(Attribute::NoReturn),
"MustProgress" => Ok(Attribute::MustProgress),
_ => Err(value.to_owned()),
}
}
}
@@ -0,0 +1,39 @@
//! 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 PolkaVM text assembly.
pub assembly_text: String,
/// The metadata hash.
pub metadata_hash: Option<[u8; revive_common::BYTE_LENGTH_FIELD]>,
/// The PolkaVM binary bytecode.
pub bytecode: Vec<u8>,
/// The PolkaVM 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; revive_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,21 @@
//! 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,97 @@
//! 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,27 @@
//! The LLVM IR generator EVM legacy assembly data.
use crate::polkavm::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,34 @@
//! The LLVM IR generator function block key.
use crate::polkavm::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,18 @@
//! 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,52 @@
//! 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,20 @@
//! 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,74 @@
//! The LLVM function EVM legacy assembly data.
use std::collections::BTreeMap;
use crate::polkavm::context::function::block::evmla_data::key::Key as BlockKey;
use crate::polkavm::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,138 @@
//! The LLVM intrinsic functions.
use inkwell::types::BasicType;
use crate::polkavm::context::address_space::AddressSpace;
use crate::polkavm::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 field_type = llvm.custom_width_int_type(revive_common::BIT_LENGTH_FIELD as u32);
let _stack_field_pointer_type = llvm.ptr_type(AddressSpace::Stack.into());
let heap_field_pointer_type = llvm.ptr_type(AddressSpace::Heap.into());
let generic_byte_pointer_type = llvm.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(revive_common::BIT_LENGTH_FIELD as u32);
match name {
name if name == Self::FUNCTION_MEMORY_COPY => vec![
llvm.ptr_type(AddressSpace::Heap.into())
.as_basic_type_enum(),
llvm.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![
llvm.ptr_type(AddressSpace::Heap.into())
.as_basic_type_enum(),
llvm.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,649 @@
//! The LLVM runtime functions.
use inkwell::types::BasicType;
use crate::optimizer::Optimizer;
use crate::polkavm::context::address_space::AddressSpace;
use crate::polkavm::context::function::declaration::Declaration as FunctionDeclaration;
use crate::polkavm::context::function::Function;
/// 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 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_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.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(revive_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(revive_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(revive_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(revive_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(revive_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(revive_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(revive_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(revive_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(revive_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(revive_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(revive_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(revive_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(revive_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(revive_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(revive_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(revive_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::define(module, Self::FUNCTION_ADDMOD).expect("should be declared in stdlib");
Function::set_default_attributes(llvm, add_mod, optimizer);
Function::set_pure_function_attributes(llvm, add_mod);
let mul_mod =
Self::define(module, Self::FUNCTION_MULMOD).expect("should be declared in stdlib");
Function::set_default_attributes(llvm, mul_mod, optimizer);
Function::set_pure_function_attributes(llvm, mul_mod);
let exp = Self::define(module, Self::FUNCTION_EXP).expect("should be declared in stdlib");
Function::set_default_attributes(llvm, exp, optimizer);
Function::set_pure_function_attributes(llvm, exp);
let sign_extend =
Self::define(module, 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 sha3 = Self::declare(
module,
Self::FUNCTION_SHA3,
llvm.custom_width_int_type(revive_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.ptr_type(AddressSpace::Heap.into())
.as_basic_type_enum()
.into(),
llvm.custom_width_int_type(revive_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into(),
llvm.custom_width_int_type(revive_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(revive_common::BIT_LENGTH_FIELD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(revive_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into(),
llvm.custom_width_int_type(revive_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into(),
llvm.custom_width_int_type(revive_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into(),
llvm.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);
let external_call_arguments: Vec<inkwell::types::BasicMetadataTypeEnum> = vec![
llvm.custom_width_int_type(revive_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into();
crate::polkavm::context::function::runtime::entry::Entry::MANDATORY_ARGUMENTS_COUNT
+ crate::polkavm::EXTRA_ABI_DATA_SIZE
];
let mut mimic_call_arguments = external_call_arguments.clone();
mimic_call_arguments.push(
llvm.custom_width_int_type(revive_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into(),
);
let mut external_call_arguments_by_ref: Vec<inkwell::types::BasicMetadataTypeEnum> = vec![
llvm.ptr_type(AddressSpace::Generic.into())
.as_basic_type_enum()
.into(),
llvm.custom_width_int_type(revive_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(
revive_common::BIT_LENGTH_FIELD as u32
)
.as_basic_type_enum()
.into();
crate::polkavm::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(revive_common::BIT_LENGTH_FIELD as u32)
.as_basic_type_enum()
.into(),
);
let external_call_result_type = llvm
.struct_type(
&[
llvm.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(revive_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(revive_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,
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)
}
/// Create the function definition from an existing symbol.
pub fn define(
module: &inkwell::module::Module<'ctx>,
name: &str,
) -> Option<FunctionDeclaration<'ctx>> {
let value = module.get_function(name)?;
value.set_linkage(inkwell::module::Linkage::External);
FunctionDeclaration::new(value.get_type(), value).into()
}
/// 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,378 @@
//! 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::optimizer::settings::size_level::SizeLevel;
use crate::optimizer::Optimizer;
use crate::polkavm::context::attribute::Attribute;
use crate::polkavm::context::pointer::Pointer;
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::Memory => todo!("`memory` attributes are not yet implemented"),
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),
),
}
}
}
/// Remove specified attributes existing on the given declaration.
pub fn remove_attributes(declaration: Declaration, attributes: &[Attribute]) {
for attribute in attributes.iter().filter(|attribute| {
declaration
.value
.get_enum_attribute(
inkwell::attributes::AttributeLoc::Function,
**attribute as u32,
)
.is_some()
}) {
declaration.value.remove_enum_attribute(
inkwell::attributes::AttributeLoc::Function,
*attribute as u32,
);
}
}
/// 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::remove_attributes(
declaration,
&[Attribute::OptimizeForSize, Attribute::AlwaysInline],
);
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,
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,59 @@
//! The LLVM IR generator function return entity.
use crate::polkavm::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 {
revive_common::BYTE_LENGTH_FIELD
* match self {
Self::None => 0,
Self::Primitive { .. } => 1,
Self::Compound { size, .. } => *size,
}
}
}
@@ -0,0 +1,244 @@
//! The `default_call` function.
use inkwell::types::BasicType;
use crate::polkavm::context::function::declaration::Declaration as FunctionDeclaration;
use crate::polkavm::context::function::llvm_runtime::LLVMRuntime;
use crate::polkavm::context::function::Function;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
use crate::polkavm::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,
}
#[allow(unused)]
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::polkavm::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::polkavm::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::polkavm::GLOBAL_RETURN_DATA_POINTER,
);
context.write_abi_data_size(
result_abi_data_pointer,
crate::polkavm::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,98 @@
//! The deploy code function.
use std::marker::PhantomData;
use crate::polkavm::context::address_space::AddressSpace;
use crate::polkavm::context::code_type::CodeType;
use crate::polkavm::context::function::runtime::Runtime;
use crate::polkavm::context::pointer::Pointer;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
use crate::polkavm::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() / revive_common::BYTE_LENGTH_FIELD {
let offset = (crate::polkavm::r#const::HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA
as usize)
+ (1 + index) * 2 * revive_common::BYTE_LENGTH_FIELD;
let value = index * revive_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,324 @@
//! The `deployer_call` function.
use inkwell::types::BasicType;
use crate::polkavm::context::address_space::AddressSpace;
use crate::polkavm::context::function::Function;
use crate::polkavm::context::pointer::Pointer;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
use crate::polkavm::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::polkavm::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(revive_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((revive_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::polkavm::DEPLOYER_CALL_HEADER_SIZE
- (revive_common::BYTE_LENGTH_X32 + revive_common::BYTE_LENGTH_FIELD))
as u64,
),
)?;
let arguments_length_offset = context.builder().build_int_add(
arguments_offset_offset,
context.field_const(revive_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::polkavm::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
.llvm()
.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::polkavm::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::polkavm::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::polkavm::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(revive_common::BIT_LENGTH_X32)
.const_zero(),
],
context
.llvm()
.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(revive_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::polkavm::GLOBAL_RETURN_DATA_POINTER,
);
context.write_abi_data_size(
result_abi_data_pointer,
crate::polkavm::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,276 @@
//! The entry function.
use inkwell::types::BasicType;
use crate::polkavm::context::address_space::AddressSpace;
use crate::polkavm::context::function::runtime::Runtime;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
use crate::polkavm::WriteLLVM;
use crate::PolkaVMPointer 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;
/// Reserve 1kb for calldata.
pub const MAX_CALLDATA_SIZE: usize = 1024;
/// 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>) -> anyhow::Result<()>
where
D: Dependency + Clone,
{
let calldata_type = context.array_type(context.byte_type(), Self::MAX_CALLDATA_SIZE);
context.set_global(
crate::polkavm::GLOBAL_CALLDATA_POINTER,
calldata_type,
AddressSpace::Stack,
calldata_type.get_undef(),
);
context.set_global(
crate::polkavm::GLOBAL_HEAP_MEMORY_POINTER,
context.llvm().ptr_type(AddressSpace::Generic.into()),
AddressSpace::Stack,
context.xlen_type().get_undef(),
);
context.build_store(
context
.get_global(crate::polkavm::GLOBAL_HEAP_MEMORY_POINTER)?
.into(),
context.build_sbrk(context.integer_const(32, 0))?,
)?;
context.set_global(
crate::polkavm::GLOBAL_CALLDATA_SIZE,
context.field_type(),
AddressSpace::Stack,
context.field_undef(),
);
context.set_global(
crate::polkavm::GLOBAL_RETURN_DATA_SIZE,
context.field_type(),
AddressSpace::Stack,
context.field_const(0),
);
context.set_global(
crate::polkavm::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::polkavm::EXTRA_ABI_DATA_SIZE,
);
context.set_global(
crate::polkavm::GLOBAL_EXTRA_ABI_DATA,
extra_abi_data_type,
AddressSpace::Stack,
extra_abi_data_type.const_zero(),
);
Ok(())
}
/// 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::polkavm::GLOBAL_CALLDATA_POINTER)?
.value
.as_pointer_value();
let input_pointer_casted = context.builder.build_ptr_to_int(
input_pointer,
context.xlen_type(),
"input_pointer_casted",
)?;
let length_pointer = context.build_alloca(context.xlen_type(), "len_ptr");
let length_pointer_casted = context.builder.build_ptr_to_int(
length_pointer.value,
context.xlen_type(),
"length_pointer_casted",
)?;
context.build_store(
length_pointer,
context.integer_const(32, Self::MAX_CALLDATA_SIZE as u64),
)?;
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::polkavm::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
.llvm()
.ptr_type(AddressSpace::Generic.into())
.as_basic_type_enum(),
"return_data_abi_initializer",
);
context.write_abi_pointer(
calldata_end_pointer,
crate::polkavm::GLOBAL_RETURN_DATA_POINTER,
);
context.write_abi_pointer(calldata_end_pointer, crate::polkavm::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::polkavm::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::PolkaVMFunction::set_attributes(
context.llvm(),
entry,
vec![crate::PolkaVMAttribute::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,90 @@
//! 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::polkavm::context::address_space::AddressSpace;
use crate::polkavm::context::function::declaration::Declaration as FunctionDeclaration;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
use crate::polkavm::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,79 @@
//! The runtime code function.
use std::marker::PhantomData;
use crate::polkavm::context::code_type::CodeType;
use crate::polkavm::context::function::runtime::Runtime;
use crate::polkavm::context::Context;
use crate::polkavm::Dependency;
use crate::polkavm::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,41 @@
//! 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,42 @@
//! 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,57 @@
//! The LLVM global value.
use inkwell::types::BasicType;
use inkwell::values::BasicValue;
use crate::polkavm::context::address_space::AddressSpace;
use crate::polkavm::context::Context;
use crate::PolkaVMDependency;
/// 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: PolkaVMDependency + 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,27 @@
//! 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,122 @@
//! The LLVM pointer.
use inkwell::types::BasicType;
use crate::polkavm::context::address_space::AddressSpace;
use crate::polkavm::context::global::Global;
use crate::polkavm::context::Context;
use crate::polkavm::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 offset = context.safe_truncate_int_to_i32(offset).unwrap();
let value = context
.builder
.build_int_to_ptr(offset, context.llvm().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,
context.llvm().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,44 @@
//! 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() * revive_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 * revive_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,134 @@
//! The LLVM IR generator context tests.
use crate::optimizer::settings::Settings as OptimizerSettings;
use crate::optimizer::Optimizer;
use crate::polkavm::context::attribute::Attribute;
use crate::polkavm::context::Context;
use crate::polkavm::DummyDependency;
pub fn create_context(
llvm: &inkwell::context::Context,
optimizer_settings: OptimizerSettings,
) -> Context<DummyDependency> {
crate::polkavm::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,37 @@
//! 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,77 @@
//! 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,132 @@
//! Translates the arithmetic operations.
use inkwell::values::BasicValue;
use crate::polkavm::context::Context;
use crate::polkavm::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,219 @@
//! Translates the bitwise operations.
use inkwell::values::BasicValue;
use crate::polkavm::context::Context;
use crate::polkavm::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((revive_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((revive_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((revive_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((revive_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"))
}
+109
View File
@@ -0,0 +1,109 @@
//! Translates a contract call.
use inkwell::values::BasicValue;
use crate::polkavm::context::argument::Argument;
use crate::polkavm::context::function::declaration::Declaration as FunctionDeclaration;
use crate::polkavm::context::Context;
use crate::polkavm::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!();
/*
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())
}
@@ -0,0 +1,76 @@
//! Translates the calldata instructions.
use crate::polkavm::context::address_space::AddressSpace;
use crate::polkavm::context::pointer::Pointer;
use crate::polkavm::context::Context;
use crate::polkavm::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::polkavm::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::polkavm::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,
{
let offset = context.safe_truncate_int_to_i32(destination_offset)?;
let size = context.safe_truncate_int_to_i32(size)?;
let destination = context.build_heap_gep(offset, size)?;
let calldata_pointer = context
.get_global(crate::polkavm::GLOBAL_CALLDATA_POINTER)?
.value
.as_pointer_value();
let source = context.build_gep(
Pointer::new(context.byte_type(), AddressSpace::Stack, calldata_pointer),
&[context.safe_truncate_int_to_i32(source_offset)?],
context.byte_type(),
"calldata_pointer_with_offset",
);
context.build_memcpy(
context.intrinsics().memory_copy_from_generic,
destination,
source,
size,
"calldata_copy_memcpy_from_child",
)
}
@@ -0,0 +1,31 @@
//! Translates the comparison operations.
use inkwell::values::BasicValue;
use crate::polkavm::context::Context;
use crate::polkavm::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,134 @@
//! Translates the context getter instructions.
use inkwell::values::BasicValue;
use crate::polkavm::context::Context;
use crate::polkavm::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,
{
todo!()
}
/// 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,
{
todo!()
}
/// 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,
{
todo!()
}
/// 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,
{
todo!()
}
/// 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,
{
todo!()
}
/// 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,
{
todo!()
}
/// 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,
{
todo!()
}
/// Translates the `difficulty` instruction.
pub fn difficulty<'ctx, D>(
_context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
todo!()
}
/// Translates the `coinbase` instruction.
pub fn coinbase<'ctx, D>(
_context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
todo!()
}
/// Translates the `basefee` instruction.
pub fn basefee<'ctx, D>(
_context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
todo!()
}
/// Translates the `msize` instruction.
pub fn msize<'ctx, D>(
context: &mut Context<'ctx, D>,
) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
D: Dependency + Clone,
{
let heap_end = context.build_sbrk(context.xlen_type().const_zero())?;
let heap_start = context
.get_global(crate::polkavm::GLOBAL_HEAP_MEMORY_POINTER)?
.value
.as_pointer_value();
let heap_size = context.builder().build_int_nuw_sub(
context
.builder()
.build_ptr_to_int(heap_end, context.xlen_type(), "heap_end")?,
context
.builder()
.build_ptr_to_int(heap_start, context.xlen_type(), "heap_start")?,
"heap_size",
)?;
Ok(context
.builder()
.build_int_z_extend(heap_size, context.field_type(), "heap_size_extended")?
.as_basic_value_enum())
}
@@ -0,0 +1,170 @@
//! Translates the contract creation instructions.
use inkwell::values::BasicValue;
use num::Zero;
use crate::polkavm::context::argument::Argument;
use crate::polkavm::context::code_type::CodeType;
use crate::polkavm::context::function::runtime::Runtime;
use crate::polkavm::context::Context;
use crate::polkavm::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::polkavm::utils::keccak256(crate::polkavm::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::polkavm::utils::keccak256(crate::polkavm::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::polkavm::DEPLOYER_CALL_HEADER_SIZE);
let size_value = context
.field_const(crate::polkavm::DEPLOYER_CALL_HEADER_SIZE as u64)
.as_basic_value_enum();
Ok(Argument::new_with_constant(size_value, size_bigint))
}
@@ -0,0 +1,47 @@
//! Translates the cryptographic operations.
use crate::polkavm::context::Context;
use crate::polkavm::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,
{
let offset_casted = context.safe_truncate_int_to_i32(offset)?;
let length_casted = context.safe_truncate_int_to_i32(length)?;
let input_pointer = context.build_heap_gep(offset_casted, length_casted)?;
let input_pointer_casted = context.builder().build_ptr_to_int(
input_pointer.value,
context.xlen_type(),
"input_pointer_casted",
)?;
let output_pointer = context.build_alloca(context.field_type(), "output_pointer");
let output_pointer_casted = context.builder().build_ptr_to_int(
output_pointer.value,
context.xlen_type(),
"output_pointer_casted",
)?;
let function = context
.module()
.get_function("hash_keccak_256")
.expect("is declared");
context.builder().build_call(
function,
&[
input_pointer_casted.into(),
length_casted.into(),
output_pointer_casted.into(),
],
"call_seal_hash_keccak_256",
)?;
Ok(context.build_byte_swap(context.build_load(output_pointer, "sha3_output")?))
}
@@ -0,0 +1,76 @@
//! Translates the value and balance operations.
use inkwell::values::BasicValue;
use crate::polkavm::context::Context;
use crate::polkavm::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,
{
let output_pointer = context.build_alloca(context.value_type(), "output_pointer");
let output_pointer_casted = context.builder().build_ptr_to_int(
output_pointer.value,
context.xlen_type(),
"output_pointer_casted",
)?;
let output_length_pointer = context.build_alloca(context.xlen_type(), "output_len_pointer");
let output_length_pointer_casted = context.builder().build_ptr_to_int(
output_length_pointer.value,
context.xlen_type(),
"output_pointer_casted",
)?;
context.build_store(
output_length_pointer,
context.integer_const(
crate::polkavm::XLEN,
revive_common::BYTE_LENGTH_VALUE as u64,
),
)?;
context.builder().build_call(
context
.module()
.get_function("value_transferred")
.expect("is declared"),
&[
output_pointer_casted.into(),
output_length_pointer_casted.into(),
],
"call_seal_value_transferred",
)?;
let value = context.build_load(output_pointer, "transferred_value")?;
let value_extended = context.builder().build_int_z_extend(
value.into_int_value(),
context.field_type(),
"transferred_value_extended",
)?;
Ok(value_extended.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,
{
todo!()
}
@@ -0,0 +1,72 @@
//! Translates a log or event call.
use crate::polkavm::context::Context;
use crate::polkavm::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::polkavm::evm::ether_gas::gas(context)?.into_int_value();
let abi_data = crate::polkavm::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::polkavm::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::polkavm::evm::r#return::revert(context, context.field_const(0), context.field_const(0))?;
context.set_basic_block(join_block);
*/
Ok(())
}
@@ -0,0 +1,26 @@
//! Translates the external code operations.
use crate::polkavm::context::Context;
use crate::polkavm::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,
{
todo!()
}
/// 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,
{
todo!()
}
@@ -0,0 +1,111 @@
//! Translates the contract immutable operations.
use crate::polkavm::context::address_space::AddressSpace;
use crate::polkavm::context::code_type::CodeType;
use crate::polkavm::context::pointer::Pointer;
use crate::polkavm::context::Context;
use crate::polkavm::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::polkavm::HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA
+ (3 * revive_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::polkavm::HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA
+ (2 * revive_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(revive_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");
}
}
}
@@ -0,0 +1,88 @@
//! Translates the mathematical operations.
use inkwell::values::BasicValue;
use crate::polkavm::context::Context;
use crate::polkavm::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,70 @@
//! Translates the heap memory operations.
use crate::polkavm::context::address_space::AddressSpace;
use crate::polkavm::context::pointer::Pointer;
use crate::polkavm::context::Context;
use crate::polkavm::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 byte_type = context.byte_type();
let value = context
.builder()
.build_int_truncate(value, byte_type, "mstore8_value")?;
let pointer = Pointer::new_with_offset(
context,
AddressSpace::Heap,
byte_type,
offset,
"mstore8_destination",
);
context.build_store(pointer, value)
}
@@ -0,0 +1,19 @@
//! 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;
@@ -0,0 +1,115 @@
//! Translates the transaction return operations.
use crate::polkavm::context::address_space::AddressSpace;
use crate::polkavm::context::code_type::CodeType;
use crate::polkavm::context::pointer::Pointer;
use crate::polkavm::context::Context;
use crate::polkavm::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::polkavm::HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA),
"immutables_offset_pointer",
);
context.build_store(
immutables_offset_pointer,
context.field_const(revive_common::BYTE_LENGTH_FIELD as u64),
)?;
let immutables_number_pointer = Pointer::new_with_offset(
context,
AddressSpace::HeapAuxiliary,
context.field_type(),
context.field_const(
crate::polkavm::HEAP_AUX_OFFSET_CONSTRUCTOR_RETURN_DATA
+ (revive_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 / revive_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((revive_common::BYTE_LENGTH_FIELD * 2) as u64),
"return_data_length",
)?;
context.build_exit(
context.integer_const(32, 0),
context.field_const(crate::polkavm::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::polkavm::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,87 @@
//! Translates the return data instructions.
use inkwell::types::BasicType;
use inkwell::values::BasicValue;
use crate::polkavm::context::address_space::AddressSpace;
use crate::polkavm::context::pointer::Pointer;
use crate::polkavm::context::Context;
use crate::polkavm::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::polkavm::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::polkavm::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::polkavm::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",
)?;
todo!("Build heap GEP to allocate if necessary")
}
@@ -0,0 +1,82 @@
//! Translates the storage operations.
use crate::polkavm::context::address_space::AddressSpace;
use crate::polkavm::context::pointer::Pointer;
use crate::polkavm::context::Context;
use crate::polkavm::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,29 @@
//! 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),
}
}
}
+160
View File
@@ -0,0 +1,160 @@
//! The LLVM context library.
pub mod r#const;
pub mod context;
pub mod evm;
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 PolkaVM target machine.
pub fn initialize_target() {
inkwell::targets::Target::initialize_riscv(&Default::default());
}
/// Builds PolkaVM assembly text.
pub fn build_assembly_text(
contract_path: &str,
assembly_text: &str,
_metadata_hash: Option<[u8; revive_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())
}
}
+218
View File
@@ -0,0 +1,218 @@
//! Some LLVM IR generator utilies.
use inkwell::values::BasicValue;
use crate::polkavm::context::address_space::AddressSpace;
use crate::polkavm::context::function::llvm_runtime::LLVMRuntime;
use crate::polkavm::context::Context;
use crate::polkavm::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
.llvm()
.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::polkavm::context::function::runtime::entry::Entry::MANDATORY_ARGUMENTS_COUNT
+ crate::polkavm::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::polkavm::utils::clamp(
context,
input_offset,
context.field_const(u32::MAX as u64),
"abi_data_input_offset",
)?;
let input_length = crate::polkavm::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::polkavm::evm::ether_gas::gas(context)?.into_int_value(),
};
let gas = crate::polkavm::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((revive_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((revive_common::BIT_LENGTH_X32 * 3) as u64),
"abi_data_input_length_shifted",
)?;
let gas_shifted = context.builder().build_left_shift(
gas,
context.field_const((revive_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((revive_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(
((revive_common::BIT_LENGTH_X32 * 7) + (revive_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::polkavm::EXTRA_ABI_DATA_SIZE]
where
D: Dependency + Clone,
{
let mut padded_data = initial_data;
padded_data.extend(vec![
context.field_undef();
crate::polkavm::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"
);
}
}