diff --git a/CHANGELOG.md b/CHANGELOG.md index 746698c..6980689 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,10 @@ Supported `polkadot-sdk` rev:`c29e72a8628835e34deb6aa7db9a78a2e4eabcee` ### Added - The `revive-runner` helper utility binary which helps to run contracts locally without a blockchain node. +- Allow configuration of the EVM heap memory size and stack size via CLI flags and JSON input settings. ### Changed +- The default PVM stack memory size was increased from 16kb to 32kb. ### Fixed - Constructors avoid storing zero sized immutable data on exit. diff --git a/crates/integration/codesize.json b/crates/integration/codesize.json index 17305de..11e9545 100644 --- a/crates/integration/codesize.json +++ b/crates/integration/codesize.json @@ -1,10 +1,10 @@ { - "Baseline": 938, - "Computation": 2281, - "DivisionArithmetics": 8848, - "ERC20": 18307, - "Events": 1639, - "FibonacciIterative": 1496, - "Flipper": 2098, - "SHA1": 8242 + "Baseline": 939, + "Computation": 2282, + "DivisionArithmetics": 8849, + "ERC20": 18308, + "Events": 1640, + "FibonacciIterative": 1497, + "Flipper": 2099, + "SHA1": 8243 } \ No newline at end of file diff --git a/crates/llvm-context/src/lib.rs b/crates/llvm-context/src/lib.rs index d5c49a1..55148d8 100644 --- a/crates/llvm-context/src/lib.rs +++ b/crates/llvm-context/src/lib.rs @@ -5,6 +5,7 @@ use std::sync::OnceLock; pub use self::debug_config::ir_type::IRType as DebugConfigIR; pub use self::debug_config::DebugConfig; +pub use self::memory::MemoryConfig; pub use self::optimizer::settings::size_level::SizeLevel as OptimizerSettingsSizeLevel; pub use self::optimizer::settings::Settings as OptimizerSettings; pub use self::optimizer::Optimizer; @@ -61,7 +62,6 @@ pub use self::polkavm::evm::ext_code as polkavm_evm_ext_code; pub use self::polkavm::evm::immutable as polkavm_evm_immutable; pub use self::polkavm::evm::immutable::Load as PolkaVMLoadImmutableDataFunction; pub use self::polkavm::evm::immutable::Store as PolkaVMStoreImmutableDataFunction; - pub use self::polkavm::evm::math as polkavm_evm_math; pub use self::polkavm::evm::memory as polkavm_evm_memory; pub use self::polkavm::evm::r#return as polkavm_evm_return; @@ -76,6 +76,7 @@ pub use self::target_machine::target::Target; pub use self::target_machine::TargetMachine; pub(crate) mod debug_config; +pub(crate) mod memory; pub(crate) mod optimizer; pub(crate) mod polkavm; pub(crate) mod target_machine; diff --git a/crates/llvm-context/src/memory.rs b/crates/llvm-context/src/memory.rs new file mode 100644 index 0000000..87fc695 --- /dev/null +++ b/crates/llvm-context/src/memory.rs @@ -0,0 +1,21 @@ +//! The compile time PolkaVM memory configuration settings. + +use serde::{Deserialize, Serialize}; + +/// The PolkaVM memory configuration. +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +pub struct MemoryConfig { + /// The emulated EVM linear heap memory size in bytes. + pub heap_size: u32, + /// The PVM stack size in bytes. + pub stack_size: u32, +} + +impl Default for MemoryConfig { + fn default() -> Self { + Self { + heap_size: 64 * 1024, + stack_size: 32 * 1024, + } + } +} 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 5823fd7..33600ba 100644 --- a/crates/llvm-context/src/polkavm/context/function/runtime/entry.rs +++ b/crates/llvm-context/src/polkavm/context/function/runtime/entry.rs @@ -38,7 +38,9 @@ impl Entry { context.xlen_type().const_zero(), ); - let heap_memory_type = context.byte_type().array_type(context.heap_size); + let heap_memory_type = context + .byte_type() + .array_type(context.memory_config.heap_size); context.set_global( crate::polkavm::GLOBAL_HEAP_MEMORY, heap_memory_type, diff --git a/crates/llvm-context/src/polkavm/context/mod.rs b/crates/llvm-context/src/polkavm/context/mod.rs index 8b8f279..bcebee0 100644 --- a/crates/llvm-context/src/polkavm/context/mod.rs +++ b/crates/llvm-context/src/polkavm/context/mod.rs @@ -26,6 +26,7 @@ use inkwell::debug_info::DIScope; use inkwell::types::BasicType; use inkwell::values::BasicValue; +use crate::memory::MemoryConfig; use crate::optimizer::settings::Settings as OptimizerSettings; use crate::optimizer::Optimizer; use crate::polkavm::DebugConfig; @@ -86,8 +87,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 PVM memory configuration. + memory_config: MemoryConfig, /// 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 @@ -119,9 +120,6 @@ where /// The loop stack default capacity. const LOOP_STACK_INITIAL_CAPACITY: usize = 16; - /// The PolkaVM minimum stack size. - const POLKAVM_STACK_SIZE: u32 = 0x4000; - /// Link in the stdlib module. fn link_stdlib_module( llvm: &'ctx inkwell::context::Context, @@ -221,6 +219,7 @@ where } /// Initializes a new LLVM context. + #[allow(clippy::too_many_arguments)] pub fn new( llvm: &'ctx inkwell::context::Context, module: inkwell::module::Module<'ctx>, @@ -229,11 +228,12 @@ where include_metadata_hash: bool, debug_config: DebugConfig, llvm_arguments: &'ctx [String], + memory_config: MemoryConfig, ) -> Self { Self::set_data_layout(llvm, &module); Self::link_stdlib_module(llvm, &module); Self::link_polkavm_imports(llvm, &module); - Self::set_polkavm_stack_size(llvm, &module, Self::POLKAVM_STACK_SIZE); + Self::set_polkavm_stack_size(llvm, &module, memory_config.stack_size); Self::set_module_flags(llvm, &module); let intrinsics = Intrinsics::new(llvm, &module); @@ -257,7 +257,7 @@ where current_function: None, loop_stack: Vec::with_capacity(Self::LOOP_STACK_INITIAL_CAPACITY), llvm_arguments, - heap_size: 65536, + memory_config, dependency_manager, include_metadata_hash, @@ -648,6 +648,7 @@ where self.include_metadata_hash, self.debug_config.clone(), self.llvm_arguments, + self.memory_config, ) }) } @@ -1442,6 +1443,7 @@ where } pub fn heap_size(&self) -> inkwell::values::IntValue<'ctx> { - self.xlen_type().const_int(self.heap_size as u64, false) + self.xlen_type() + .const_int(self.memory_config.heap_size as u64, false) } } diff --git a/crates/llvm-context/src/polkavm/context/tests.rs b/crates/llvm-context/src/polkavm/context/tests.rs index 73a2f72..9060a3d 100644 --- a/crates/llvm-context/src/polkavm/context/tests.rs +++ b/crates/llvm-context/src/polkavm/context/tests.rs @@ -23,6 +23,7 @@ pub fn create_context( true, Default::default(), Default::default(), + Default::default(), ) } diff --git a/crates/llvm-context/src/polkavm/mod.rs b/crates/llvm-context/src/polkavm/mod.rs index f2ad22e..68d32d9 100644 --- a/crates/llvm-context/src/polkavm/mod.rs +++ b/crates/llvm-context/src/polkavm/mod.rs @@ -7,6 +7,7 @@ pub mod evm; pub use self::r#const::*; use crate::debug_config::DebugConfig; +use crate::memory::MemoryConfig; use crate::optimizer::settings::Settings as OptimizerSettings; use anyhow::Context as AnyhowContext; @@ -90,6 +91,7 @@ pub trait Dependency { include_metadata_hash: bool, debug_config: DebugConfig, llvm_arguments: &[String], + memory_config: MemoryConfig, ) -> anyhow::Result; /// Resolves a full contract path. @@ -111,6 +113,7 @@ impl Dependency for DummyDependency { _include_metadata_hash: bool, _debug_config: DebugConfig, _llvm_arguments: &[String], + _memory_config: MemoryConfig, ) -> anyhow::Result { Ok(String::new()) } diff --git a/crates/solidity/src/lib.rs b/crates/solidity/src/lib.rs index 2c5ff10..608c992 100644 --- a/crates/solidity/src/lib.rs +++ b/crates/solidity/src/lib.rs @@ -55,6 +55,7 @@ pub fn yul( include_metadata_hash: bool, debug_config: revive_llvm_context::DebugConfig, llvm_arguments: &[String], + memory_config: revive_llvm_context::MemoryConfig, ) -> anyhow::Result { let path = match input_files.len() { 1 => input_files.first().expect("Always exists"), @@ -80,6 +81,7 @@ pub fn yul( include_metadata_hash, debug_config, llvm_arguments, + memory_config, )?; Ok(build) @@ -92,6 +94,7 @@ pub fn llvm_ir( include_metadata_hash: bool, debug_config: revive_llvm_context::DebugConfig, llvm_arguments: &[String], + memory_config: revive_llvm_context::MemoryConfig, ) -> anyhow::Result { let path = match input_files.len() { 1 => input_files.first().expect("Always exists"), @@ -109,6 +112,7 @@ pub fn llvm_ir( include_metadata_hash, debug_config, llvm_arguments, + memory_config, )?; Ok(build) @@ -131,6 +135,7 @@ pub fn standard_output( suppressed_warnings: Option>, debug_config: revive_llvm_context::DebugConfig, llvm_arguments: &[String], + memory_config: revive_llvm_context::MemoryConfig, ) -> anyhow::Result { let solc_version = solc.version()?; @@ -189,12 +194,14 @@ pub fn standard_output( include_metadata_hash, debug_config, llvm_arguments, + memory_config, )?; Ok(build) } /// Runs the standard JSON mode. +#[allow(clippy::too_many_arguments)] pub fn standard_json( solc: &mut T, detect_missing_libraries: bool, @@ -203,6 +210,7 @@ pub fn standard_json( allow_paths: Option, debug_config: revive_llvm_context::DebugConfig, llvm_arguments: &[String], + memory_config: revive_llvm_context::MemoryConfig, ) -> anyhow::Result<()> { let solc_version = solc.version()?; @@ -250,6 +258,7 @@ pub fn standard_json( include_metadata_hash, debug_config, llvm_arguments, + memory_config, )?; build.write_to_standard_json(&mut solc_output, &solc_version)?; } @@ -277,6 +286,7 @@ pub fn combined_json( output_directory: Option, overwrite: bool, llvm_arguments: &[String], + memory_config: revive_llvm_context::MemoryConfig, ) -> anyhow::Result<()> { let build = standard_output( input_files, @@ -293,6 +303,7 @@ pub fn combined_json( suppressed_warnings, debug_config, llvm_arguments, + memory_config, )?; let mut combined_json = solc.combined_json(input_files, format.as_str())?; diff --git a/crates/solidity/src/process/input.rs b/crates/solidity/src/process/input.rs index b2e3825..a1e2b6d 100644 --- a/crates/solidity/src/process/input.rs +++ b/crates/solidity/src/process/input.rs @@ -22,6 +22,8 @@ pub struct Input { pub debug_config: revive_llvm_context::DebugConfig, /// The extra LLVM arguments give used for manual control. pub llvm_arguments: Vec, + /// The PVM memory configuration. + pub memory_config: revive_llvm_context::MemoryConfig, } impl Input { @@ -33,6 +35,7 @@ impl Input { optimizer_settings: revive_llvm_context::OptimizerSettings, debug_config: revive_llvm_context::DebugConfig, llvm_arguments: Vec, + memory_config: revive_llvm_context::MemoryConfig, ) -> Self { Self { contract, @@ -41,6 +44,7 @@ impl Input { optimizer_settings, debug_config, llvm_arguments, + memory_config, } } } diff --git a/crates/solidity/src/process/mod.rs b/crates/solidity/src/process/mod.rs index 815a8fd..a98f822 100644 --- a/crates/solidity/src/process/mod.rs +++ b/crates/solidity/src/process/mod.rs @@ -50,6 +50,7 @@ pub trait Process { input.include_metadata_hash, input.debug_config, &input.llvm_arguments, + input.memory_config, ); match result { diff --git a/crates/solidity/src/project/contract/mod.rs b/crates/solidity/src/project/contract/mod.rs index 6e2f8cb..9af062d 100644 --- a/crates/solidity/src/project/contract/mod.rs +++ b/crates/solidity/src/project/contract/mod.rs @@ -78,6 +78,7 @@ impl Contract { include_metadata_hash: bool, debug_config: revive_llvm_context::DebugConfig, llvm_arguments: &[String], + memory_config: revive_llvm_context::MemoryConfig, ) -> anyhow::Result { let llvm = inkwell::context::Context::create(); let optimizer = revive_llvm_context::Optimizer::new(optimizer_settings); @@ -123,6 +124,7 @@ impl Contract { include_metadata_hash, debug_config, llvm_arguments, + memory_config, ); context.set_solidity_data(revive_llvm_context::PolkaVMContextSolidityData::default()); match self.ir { diff --git a/crates/solidity/src/project/mod.rs b/crates/solidity/src/project/mod.rs index e92c22a..40e6861 100644 --- a/crates/solidity/src/project/mod.rs +++ b/crates/solidity/src/project/mod.rs @@ -67,6 +67,7 @@ impl Project { include_metadata_hash: bool, debug_config: revive_llvm_context::DebugConfig, llvm_arguments: &[String], + memory_config: revive_llvm_context::MemoryConfig, ) -> anyhow::Result { let project = self.clone(); #[cfg(feature = "parallel")] @@ -83,6 +84,7 @@ impl Project { optimizer_settings.clone(), debug_config.clone(), llvm_arguments.to_vec(), + memory_config, ); let process_output = { #[cfg(target_os = "emscripten")] @@ -319,6 +321,7 @@ impl revive_llvm_context::PolkaVMDependency for Project { include_metadata_hash: bool, debug_config: revive_llvm_context::DebugConfig, llvm_arguments: &[String], + memory_config: revive_llvm_context::MemoryConfig, ) -> anyhow::Result { let contract_path = project.resolve_path(identifier)?; let contract = project @@ -339,6 +342,7 @@ impl revive_llvm_context::PolkaVMDependency for Project { include_metadata_hash, debug_config, llvm_arguments, + memory_config, ) .map_err(|error| { anyhow::anyhow!( diff --git a/crates/solidity/src/resolc/arguments.rs b/crates/solidity/src/resolc/arguments.rs index c1e08ed..e9c8424 100644 --- a/crates/solidity/src/resolc/arguments.rs +++ b/crates/solidity/src/resolc/arguments.rs @@ -170,6 +170,38 @@ pub struct Arguments { /// These are passed to LLVM as the command line to allow manual control. #[arg(long = "llvm-arg")] pub llvm_arguments: Vec, + + /// The emulated EVM linear heap memory static buffer size in bytes. + /// + /// Unlike the EVM, due to the lack of dynamic memory metering, PVM contracts emulate + /// the EVM heap memory with a static buffer. Consequentially, instead of infinite + /// memory with exponentially growing gas costs, PVM contracts have a finite amount + /// of memory with constant gas costs available. + /// + /// If the contract uses more heap memory than configured, it will compile fine but + /// eventually revert execution at runtime! + /// + /// You are incentiviced to keep this value as small as possible: + /// 1.Increasing the heap size will increase startup costs. + /// 2.The heap size contributes to the total memory size a contract can use, + /// which includes the contracts code size + #[arg(long = "heap-size", default_value = "65536")] + pub heap_size: u32, + + /// The contracts total stack size in bytes. + /// + /// PVM is a register machine with a traditional stack memory space for local + /// variables. This controls the total amount of stack space the contract can use. + /// + /// If the contract uses more stack memory than configured, it will compile fine but + /// eventually revert execution at runtime! + /// + /// You are incentiviced to keep this value as small as possible: + /// 1.Increasing the heap size will increase startup costs. + /// 2.The stack size contributes to the total memory size a contract can use, + /// which includes the contracts code size + #[arg(long = "stack-size", default_value = "32768")] + pub stack_size: u32, } impl Arguments { diff --git a/crates/solidity/src/resolc/main.rs b/crates/solidity/src/resolc/main.rs index 92af664..39dfcd5 100644 --- a/crates/solidity/src/resolc/main.rs +++ b/crates/solidity/src/resolc/main.rs @@ -148,6 +148,11 @@ fn main_inner() -> anyhow::Result<()> { None => true, }; + let memory_config = revive_llvm_context::MemoryConfig { + heap_size: arguments.heap_size, + stack_size: arguments.stack_size, + }; + let build = if arguments.yul { revive_solidity::yul( input_files.as_slice(), @@ -156,6 +161,7 @@ fn main_inner() -> anyhow::Result<()> { include_metadata_hash, debug_config, &arguments.llvm_arguments, + memory_config, ) } else if arguments.llvm_ir { revive_solidity::llvm_ir( @@ -164,6 +170,7 @@ fn main_inner() -> anyhow::Result<()> { include_metadata_hash, debug_config, &arguments.llvm_arguments, + memory_config, ) } else if arguments.standard_json { revive_solidity::standard_json( @@ -174,6 +181,7 @@ fn main_inner() -> anyhow::Result<()> { arguments.allow_paths, debug_config, &arguments.llvm_arguments, + memory_config, )?; return Ok(()); } else if let Some(format) = arguments.combined_json { @@ -195,6 +203,7 @@ fn main_inner() -> anyhow::Result<()> { arguments.output_directory, arguments.overwrite, &arguments.llvm_arguments, + memory_config, )?; return Ok(()); } else { @@ -213,6 +222,7 @@ fn main_inner() -> anyhow::Result<()> { suppressed_warnings, debug_config, &arguments.llvm_arguments, + memory_config, ) }?; diff --git a/crates/solidity/src/test_utils.rs b/crates/solidity/src/test_utils.rs index 72d1f88..875ed27 100644 --- a/crates/solidity/src/test_utils.rs +++ b/crates/solidity/src/test_utils.rs @@ -115,8 +115,13 @@ pub fn build_solidity_with_options( &debug_config, )?; - let build: crate::Build = - project.compile(optimizer_settings, false, debug_config, Default::default())?; + let build: crate::Build = project.compile( + optimizer_settings, + false, + debug_config, + Default::default(), + Default::default(), + )?; build.write_to_standard_json(&mut output, &solc_version)?; Ok(output) @@ -243,7 +248,13 @@ pub fn build_yul(source_code: &str) -> anyhow::Result<()> { source_code, None, )?; - let _build = project.compile(optimizer_settings, false, DEBUG_CONFIG, Default::default())?; + let _build = project.compile( + optimizer_settings, + false, + DEBUG_CONFIG, + Default::default(), + Default::default(), + )?; Ok(()) }