diff --git a/crates/integration/codesize.json b/crates/integration/codesize.json index 28819e3..17305de 100644 --- a/crates/integration/codesize.json +++ b/crates/integration/codesize.json @@ -1,10 +1,10 @@ { - "Baseline": 950, - "Computation": 2222, - "DivisionArithmetics": 8802, - "ERC20": 17602, - "Events": 1628, - "FibonacciIterative": 1485, - "Flipper": 2082, - "SHA1": 8230 + "Baseline": 938, + "Computation": 2281, + "DivisionArithmetics": 8848, + "ERC20": 18307, + "Events": 1639, + "FibonacciIterative": 1496, + "Flipper": 2098, + "SHA1": 8242 } \ No newline at end of file diff --git a/crates/llvm-context/src/lib.rs b/crates/llvm-context/src/lib.rs index 6fcd3a2..d5c49a1 100644 --- a/crates/llvm-context/src/lib.rs +++ b/crates/llvm-context/src/lib.rs @@ -28,6 +28,7 @@ pub use self::polkavm::context::function::runtime::entry::Entry as PolkaVMEntryF pub use self::polkavm::context::function::runtime::revive::Exit as PolkaVMExitFunction; pub use self::polkavm::context::function::runtime::revive::WordToPointer as PolkaVMWordToPointerFunction; pub use self::polkavm::context::function::runtime::runtime_code::RuntimeCode as PolkaVMRuntimeCodeFunction; +pub use self::polkavm::context::function::runtime::sbrk::Sbrk as PolkaVMSbrkFunction; pub use self::polkavm::context::function::runtime::FUNCTION_DEPLOY_CODE as PolkaVMFunctionDeployCode; pub use self::polkavm::context::function::runtime::FUNCTION_ENTRY as PolkaVMFunctionEntry; pub use self::polkavm::context::function::runtime::FUNCTION_RUNTIME_CODE as PolkaVMFunctionRuntimeCode; diff --git a/crates/llvm-context/src/polkavm/const/mod.rs b/crates/llvm-context/src/polkavm/const/mod.rs index 89fd234..6f36793 100644 --- a/crates/llvm-context/src/polkavm/const/mod.rs +++ b/crates/llvm-context/src/polkavm/const/mod.rs @@ -9,6 +9,12 @@ pub static XLEN: usize = revive_common::BIT_LENGTH_X32; /// The calldata size global variable name. pub static GLOBAL_CALLDATA_SIZE: &str = "calldatasize"; +/// The heap size global variable name. +pub static GLOBAL_HEAP_SIZE: &str = "__heap_size"; + +/// The heap memory global variable name. +pub static GLOBAL_HEAP_MEMORY: &str = "__heap_memory"; + /// The spill buffer global variable name. pub static GLOBAL_ADDRESS_SPILL_BUFFER: &str = "address_spill_buffer"; diff --git a/crates/llvm-context/src/polkavm/context/function/runtime/entry.rs b/crates/llvm-context/src/polkavm/context/function/runtime/entry.rs index 14d8d93..5823fd7 100644 --- a/crates/llvm-context/src/polkavm/context/function/runtime/entry.rs +++ b/crates/llvm-context/src/polkavm/context/function/runtime/entry.rs @@ -31,6 +31,21 @@ impl Entry { context.xlen_type().get_undef(), ); + context.set_global( + crate::polkavm::GLOBAL_HEAP_SIZE, + context.xlen_type(), + AddressSpace::Stack, + context.xlen_type().const_zero(), + ); + + let heap_memory_type = context.byte_type().array_type(context.heap_size); + context.set_global( + crate::polkavm::GLOBAL_HEAP_MEMORY, + heap_memory_type, + AddressSpace::Stack, + heap_memory_type.const_zero(), + ); + let address_type = context.integer_type(revive_common::BIT_LENGTH_ETH_ADDRESS); context.set_global( crate::polkavm::GLOBAL_ADDRESS_SPILL_BUFFER, diff --git a/crates/llvm-context/src/polkavm/context/function/runtime/mod.rs b/crates/llvm-context/src/polkavm/context/function/runtime/mod.rs index 3b79973..ec90f4b 100644 --- a/crates/llvm-context/src/polkavm/context/function/runtime/mod.rs +++ b/crates/llvm-context/src/polkavm/context/function/runtime/mod.rs @@ -5,6 +5,7 @@ pub mod deploy_code; pub mod entry; pub mod revive; pub mod runtime_code; +pub mod sbrk; /// The main entry function name. pub const FUNCTION_ENTRY: &str = "__entry"; diff --git a/crates/llvm-context/src/polkavm/context/function/runtime/sbrk.rs b/crates/llvm-context/src/polkavm/context/function/runtime/sbrk.rs new file mode 100644 index 0000000..1ea12b8 --- /dev/null +++ b/crates/llvm-context/src/polkavm/context/function/runtime/sbrk.rs @@ -0,0 +1,144 @@ +//! Emulates the linear EVM heap memory via a simulated `sbrk` system call. + +use inkwell::values::BasicValue; + +use crate::polkavm::context::attribute::Attribute; +use crate::polkavm::context::runtime::RuntimeFunction; +use crate::polkavm::context::Context; +use crate::polkavm::Dependency; +use crate::polkavm::WriteLLVM; + +/// Simulates the `sbrk` system call, reproducing the semantics of the EVM heap memory. +/// +/// Parameters: +/// - The `offset` into the emulated EVM heap memory. +/// - The `size` of the allocation emulated EVM heap memory. +/// +/// Returns: +/// - A pointer to the EVM heap memory at given `offset`. +/// +/// Semantics: +/// - Traps if the offset is out of bounds. +/// - Aligns the total heap memory size to the EVM word size. +/// - Traps if the memory size would be greater than the configured EVM heap memory size. +/// - Maintains the total memory size (`msize`) in global heap size value. +pub struct Sbrk; + +impl RuntimeFunction for Sbrk +where + D: Dependency + Clone, +{ + const NAME: &'static str = "__sbrk_internal"; + + const ATTRIBUTES: &'static [Attribute] = &[ + Attribute::NoFree, + Attribute::NoRecurse, + Attribute::WillReturn, + ]; + + fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> { + context.llvm().ptr_type(Default::default()).fn_type( + &[context.xlen_type().into(), context.xlen_type().into()], + false, + ) + } + + fn emit_body<'ctx>( + &self, + context: &mut Context<'ctx, D>, + ) -> anyhow::Result>> { + let offset = Self::paramater(context, 0).into_int_value(); + let size = Self::paramater(context, 1).into_int_value(); + + let trap_block = context.append_basic_block("trap"); + let offset_in_bounds_block = context.append_basic_block("offset_in_bounds"); + let is_offset_out_of_bounds = context.builder().build_int_compare( + inkwell::IntPredicate::UGE, + offset, + context.heap_size(), + "offset_out_of_bounds", + )?; + context.build_conditional_branch( + is_offset_out_of_bounds, + trap_block, + offset_in_bounds_block, + )?; + + context.set_basic_block(trap_block); + context.build_call(context.intrinsics().trap, &[], "invalid_trap"); + context.build_unreachable(); + + context.set_basic_block(offset_in_bounds_block); + let mask = context + .xlen_type() + .const_int(revive_common::BYTE_LENGTH_WORD as u64 - 1, false); + let total_size = context + .builder() + .build_int_add(offset, size, "total_size")?; + let memory_size = context.builder().build_and( + context.builder().build_int_add(total_size, mask, "mask")?, + context.builder().build_not(mask, "mask_not")?, + "memory_size", + )?; + let size_in_bounds_block = context.append_basic_block("size_in_bounds"); + let is_size_out_of_bounds = context.builder().build_int_compare( + inkwell::IntPredicate::UGT, + memory_size, + context.heap_size(), + "size_out_of_bounds", + )?; + context.build_conditional_branch( + is_size_out_of_bounds, + trap_block, + size_in_bounds_block, + )?; + + context.set_basic_block(size_in_bounds_block); + let return_block = context.append_basic_block("return_pointer"); + let new_size_block = context.append_basic_block("new_size"); + let is_new_size = context.builder().build_int_compare( + inkwell::IntPredicate::UGT, + memory_size, + context + .get_global_value(crate::polkavm::GLOBAL_HEAP_SIZE)? + .into_int_value(), + "is_new_size", + )?; + context.build_conditional_branch(is_new_size, new_size_block, return_block)?; + + context.set_basic_block(new_size_block); + context.build_store( + context.get_global(crate::polkavm::GLOBAL_HEAP_SIZE)?.into(), + memory_size, + )?; + context.build_unconditional_branch(return_block); + + context.set_basic_block(return_block); + Ok(Some( + context + .build_gep( + context + .get_global(crate::polkavm::GLOBAL_HEAP_MEMORY)? + .into(), + &[context.xlen_type().const_zero(), offset], + context.byte_type(), + "allocation_start_pointer", + ) + .value + .as_basic_value_enum(), + )) + } +} + +impl WriteLLVM for Sbrk +where + D: Dependency + Clone, +{ + fn declare(&mut self, context: &mut Context) -> anyhow::Result<()> { + >::declare(self, context) + } + + fn into_llvm(self, context: &mut Context) -> anyhow::Result<()> { + >::emit(&self, context) + } +} diff --git a/crates/llvm-context/src/polkavm/context/mod.rs b/crates/llvm-context/src/polkavm/context/mod.rs index d58623c..8b8f279 100644 --- a/crates/llvm-context/src/polkavm/context/mod.rs +++ b/crates/llvm-context/src/polkavm/context/mod.rs @@ -33,6 +33,7 @@ use crate::polkavm::Dependency; use crate::target_machine::target::Target; use crate::target_machine::TargetMachine; use crate::PolkaVMLoadHeapWordFunction; +use crate::PolkaVMSbrkFunction; use crate::PolkaVMStoreHeapWordFunction; use self::address_space::AddressSpace; @@ -85,6 +86,8 @@ where loop_stack: Vec>, /// The extra LLVM arguments that were used during target initialization. llvm_arguments: &'ctx [String], + /// The emulated EVM linear heap memory size. + heap_size: u32, /// The project dependency manager. It can be any entity implementing the trait. /// The manager is used to get information about contracts and their dependencies during @@ -254,6 +257,7 @@ where current_function: None, loop_stack: Vec::with_capacity(Self::LOOP_STACK_INITIAL_CAPACITY), llvm_arguments, + heap_size: 65536, dependency_manager, include_metadata_hash, @@ -1084,32 +1088,40 @@ where offset: inkwell::values::IntValue<'ctx>, size: inkwell::values::IntValue<'ctx>, ) -> anyhow::Result> { - Ok(self - .builder() - .build_call( - self.runtime_api_method(revive_runtime_api::polkavm_imports::SBRK), - &[offset.into(), size.into()], - "call_sbrk", - )? + let call_site_value = self.builder().build_call( + >::declaration(self).function_value(), + &[offset.into(), size.into()], + "alloc_start", + )?; + + call_site_value.add_attribute( + inkwell::attributes::AttributeLoc::Return, + self.llvm + .create_enum_attribute(Attribute::NonNull as u32, 0), + ); + call_site_value.add_attribute( + inkwell::attributes::AttributeLoc::Return, + self.llvm + .create_enum_attribute(Attribute::NoUndef as u32, 0), + ); + + Ok(call_site_value .try_as_basic_value() .left() - .expect("sbrk returns a pointer") + .unwrap_or_else(|| { + panic!( + "revive runtime function {} should return a value", + >::NAME, + ) + }) .into_pointer_value()) } /// Build a call to PolkaVM `msize` for querying the linear memory size. pub fn build_msize(&self) -> anyhow::Result> { - let memory_size_pointer = self - .module() - .get_global(revive_runtime_api::polkavm_imports::MEMORY_SIZE) - .expect("the memory size symbol should have been declared") - .as_pointer_value(); - let memory_size_value = self.builder().build_load( - self.xlen_type(), - memory_size_pointer, - "memory_size_value", - )?; - Ok(memory_size_value.into_int_value()) + Ok(self + .get_global_value(crate::polkavm::GLOBAL_HEAP_SIZE)? + .into_int_value()) } /// Returns a pointer to `offset` into the heap, allocating @@ -1428,4 +1440,8 @@ where pub fn optimizer_settings(&self) -> &OptimizerSettings { self.optimizer.settings() } + + pub fn heap_size(&self) -> inkwell::values::IntValue<'ctx> { + self.xlen_type().const_int(self.heap_size as u64, false) + } } diff --git a/crates/runtime-api/src/polkavm_imports.c b/crates/runtime-api/src/polkavm_imports.c index a97bc87..91d4e36 100644 --- a/crates/runtime-api/src/polkavm_imports.c +++ b/crates/runtime-api/src/polkavm_imports.c @@ -5,34 +5,6 @@ // Missing builtins -#define EVM_WORD_SIZE 32 -#define ALIGN(size) ((size + EVM_WORD_SIZE - 1) & ~(EVM_WORD_SIZE - 1)) -#define MAX_MEMORY_SIZE (64 * 1024) -char __memory[MAX_MEMORY_SIZE]; -uint32_t __memory_size = 0; - -void * __sbrk_internal(uint32_t offset, uint32_t size) { - if (offset >= MAX_MEMORY_SIZE || size > MAX_MEMORY_SIZE) { - POLKAVM_TRAP(); - } - - uint32_t new_size = ALIGN(offset + size); - if (new_size > MAX_MEMORY_SIZE) { - POLKAVM_TRAP(); - } - if (new_size > __memory_size) { - __memory_size = new_size; - } - - return (void *)&__memory[offset]; -} - -void * memset(void *b, int c, size_t len) { - uint8_t *dest = b; - while (len-- > 0) *dest++ = c; - return b; -} - void * memcpy(void *dst, const void *_src, size_t len) { uint8_t *dest = dst; const uint8_t *src = _src; diff --git a/crates/runtime-api/src/polkavm_imports.rs b/crates/runtime-api/src/polkavm_imports.rs index 4050bb8..42ca88a 100644 --- a/crates/runtime-api/src/polkavm_imports.rs +++ b/crates/runtime-api/src/polkavm_imports.rs @@ -2,14 +2,6 @@ use inkwell::{context::Context, memory_buffer::MemoryBuffer, module::Module, sup include!(concat!(env!("OUT_DIR"), "/polkavm_imports.rs")); -/// The emulated EVM heap memory global symbol. -pub static MEMORY: &str = "__memory"; - -/// The emulated EVM heap memory size global symbol. -pub static MEMORY_SIZE: &str = "__memory_size"; - -pub static SBRK: &str = "__sbrk_internal"; - pub static ADDRESS: &str = "address"; pub static BALANCE: &str = "balance"; @@ -78,8 +70,7 @@ pub static WEIGHT_TO_FEE: &str = "weight_to_fee"; /// All imported runtime API symbols. /// Useful for configuring common attributes and linkage. -pub static IMPORTS: [&str; 34] = [ - SBRK, +pub static IMPORTS: [&str; 33] = [ ADDRESS, BALANCE, BALANCE_OF, diff --git a/crates/solidity/src/resolc/arguments.rs b/crates/solidity/src/resolc/arguments.rs index b9cafae..c1e08ed 100644 --- a/crates/solidity/src/resolc/arguments.rs +++ b/crates/solidity/src/resolc/arguments.rs @@ -167,8 +167,8 @@ pub struct Arguments { #[arg(long = "recursive-process-input")] pub recursive_process_input: Option, - #[arg(long = "llvm-arg")] /// These are passed to LLVM as the command line to allow manual control. + #[arg(long = "llvm-arg")] pub llvm_arguments: Vec, } diff --git a/crates/solidity/src/yul/parser/statement/object.rs b/crates/solidity/src/yul/parser/statement/object.rs index 55b2284..8ba5cc4 100644 --- a/crates/solidity/src/yul/parser/statement/object.rs +++ b/crates/solidity/src/yul/parser/statement/object.rs @@ -209,6 +209,8 @@ where revive_llvm_context::PolkaVMRemainderFunction.declare(context)?; revive_llvm_context::PolkaVMSignedRemainderFunction.declare(context)?; + revive_llvm_context::PolkaVMSbrkFunction.declare(context)?; + let mut entry = revive_llvm_context::PolkaVMEntryFunction::default(); entry.declare(context)?; @@ -261,6 +263,8 @@ where revive_llvm_context::PolkaVMRemainderFunction.into_llvm(context)?; revive_llvm_context::PolkaVMSignedRemainderFunction.into_llvm(context)?; + revive_llvm_context::PolkaVMSbrkFunction.into_llvm(context)?; + Ok(()) }