diff --git a/Cargo.lock b/Cargo.lock index 9b94fc2..a9ce84e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,9 +108,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" @@ -289,9 +289,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "base16ct" @@ -394,9 +394,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.96" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd" +checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" [[package]] name = "cfg-if" @@ -1261,9 +1261,9 @@ dependencies = [ [[package]] name = "num-iter" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", @@ -1284,9 +1284,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", @@ -1360,9 +1360,9 @@ checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42" [[package]] name = "pest" -version = "2.7.9" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "311fb059dee1a7b802f036316d790138c613a4e8b180c822e3925a662e9f0c95" +checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" dependencies = [ "memchr", "thiserror", @@ -2021,18 +2021,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.199" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a" +checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.199" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc" +checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" dependencies = [ "proc-macro2", "quote", @@ -2681,18 +2681,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.32" +version = "0.7.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "087eca3c1eaf8c47b94d02790dd086cd594b912d2043d4de4bfdd466b3befb7c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.7.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "6f4b6c273f496d8fd4eaf18853e6b448760225dc030ff2c485a786859aea6393" dependencies = [ "proc-macro2", "quote", diff --git a/crates/integration/codesize.json b/crates/integration/codesize.json index b3eac7a..6f734b4 100644 --- a/crates/integration/codesize.json +++ b/crates/integration/codesize.json @@ -1,7 +1,7 @@ { - "Computation": 7380, - "ERC20": 51241, - "Baseline": 3912, "Fibonacci": 5971, - "Flipper": 4354 + "Baseline": 3912, + "Computation": 7380, + "Flipper": 4354, + "ERC20": 53186 } \ No newline at end of file diff --git a/crates/integration/src/mock_runtime.rs b/crates/integration/src/mock_runtime.rs index 55434d9..def589d 100644 --- a/crates/integration/src/mock_runtime.rs +++ b/crates/integration/src/mock_runtime.rs @@ -7,6 +7,7 @@ use polkavm::{ Caller, Config, Engine, ExportIndex, GasMeteringKind, Instance, Linker, Module, ModuleConfig, ProgramBlob, Trap, }; +use revive_llvm_context::polkavm_const::runtime_api; #[derive(Default, Clone, Debug)] pub struct State { @@ -53,7 +54,7 @@ fn link_host_functions(engine: &Engine) -> Linker { linker .func_wrap( - "input", + runtime_api::INPUT, |caller: Caller, out_ptr: u32, out_len_ptr: u32| -> Result<(), Trap> { let (mut caller, state) = caller.split(); @@ -69,7 +70,7 @@ fn link_host_functions(engine: &Engine) -> Linker { linker .func_wrap( - "seal_return", + runtime_api::RETURN, |caller: Caller, flags: u32, data_ptr: u32, data_len: u32| -> Result<(), Trap> { let (caller, state) = caller.split(); @@ -83,7 +84,7 @@ fn link_host_functions(engine: &Engine) -> Linker { linker .func_wrap( - "value_transferred", + runtime_api::VALUE_TRANSFERRED, |caller: Caller, out_ptr: u32, out_len_ptr: u32| -> Result<(), Trap> { let (mut caller, state) = caller.split(); @@ -113,7 +114,7 @@ fn link_host_functions(engine: &Engine) -> Linker { linker .func_wrap( - "set_storage", + runtime_api::SET_STORAGE, |caller: Caller, key_ptr: u32, key_len: u32, @@ -140,7 +141,7 @@ fn link_host_functions(engine: &Engine) -> Linker { linker .func_wrap( - "get_storage", + runtime_api::GET_STORAGE, |caller: Caller, key_ptr: u32, key_len: u32, @@ -169,7 +170,7 @@ fn link_host_functions(engine: &Engine) -> Linker { linker .func_wrap( - "hash_keccak_256", + runtime_api::HASH_KECCAK_256, |caller: Caller, input_ptr: u32, input_len: u32, @@ -203,7 +204,7 @@ pub fn recompile_code(code: &[u8], engine: &Engine) -> Module { } pub fn instantiate_module(module: &Module, engine: &Engine) -> (Instance, ExportIndex) { - let export = module.lookup_export("call").unwrap(); + let export = module.lookup_export(runtime_api::CALL).unwrap(); let func = link_host_functions(engine).instantiate_pre(module).unwrap(); let instance = func.instantiate().unwrap(); @@ -219,7 +220,7 @@ pub fn prepare(code: &[u8], config: Option) -> (Instance, ExportI module_config.set_gas_metering(Some(GasMeteringKind::Sync)); let module = Module::from_blob(&engine, &module_config, &blob).unwrap(); - let export = module.lookup_export("call").unwrap(); + let export = module.lookup_export(runtime_api::CALL).unwrap(); let func = link_host_functions(&engine) .instantiate_pre(&module) .unwrap(); diff --git a/crates/llvm-context/src/polkavm/const.rs b/crates/llvm-context/src/polkavm/const/mod.rs similarity index 98% rename from crates/llvm-context/src/polkavm/const.rs rename to crates/llvm-context/src/polkavm/const/mod.rs index 20b42d3..173ee40 100644 --- a/crates/llvm-context/src/polkavm/const.rs +++ b/crates/llvm-context/src/polkavm/const/mod.rs @@ -1,5 +1,8 @@ //! The LLVM context constants. +/// Runtime API methods. +pub mod runtime_api; + /// The LLVM framework version. pub const LLVM_VERSION: semver::Version = semver::Version::new(18, 1, 4); diff --git a/crates/llvm-context/src/polkavm/const/runtime_api.rs b/crates/llvm-context/src/polkavm/const/runtime_api.rs new file mode 100644 index 0000000..c66990d --- /dev/null +++ b/crates/llvm-context/src/polkavm/const/runtime_api.rs @@ -0,0 +1,37 @@ +//! Runtime API import and export symbols. + +/// The contract deploy export. +pub static CALL: &str = "call"; + +/// The contract call export. +pub static DEPLOY: &str = "deploy"; + +/// All exported symbols. +/// Useful for configuring common attributes and linkage. +pub static EXPORTS: [&str; 2] = [CALL, DEPLOY]; + +pub static GET_STORAGE: &str = "get_storage"; + +pub static HASH_KECCAK_256: &str = "hash_keccak_256"; + +pub static INPUT: &str = "input"; + +pub static RETURN: &str = "seal_return"; + +pub static SET_STORAGE: &str = "set_storage"; + +pub static VALUE_TRANSFERRED: &str = "value_transferred"; + +/// All imported runtime API symbols.. +/// Useful for configuring common attributes and linkage. +pub static IMPORTS: [&str; 6] = [ + GET_STORAGE, + HASH_KECCAK_256, + INPUT, + RETURN, + SET_STORAGE, + VALUE_TRANSFERRED, +]; + +/// PolkaVM __sbrk API symbol to extend the heap memory. +pub static SBRK: &str = "__sbrk"; 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 19ff5a9..4bd792a 100644 --- a/crates/llvm-context/src/polkavm/context/function/runtime/entry.rs +++ b/crates/llvm-context/src/polkavm/context/function/runtime/entry.rs @@ -5,6 +5,7 @@ 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::r#const::*; use crate::polkavm::Dependency; use crate::polkavm::WriteLLVM; use crate::PolkaVMPointer as Pointer; @@ -113,11 +114,10 @@ impl Entry { length_pointer, context.integer_const(32, Self::MAX_CALLDATA_SIZE as u64), )?; - context.builder().build_call( - context.module().get_function("input").expect("is declared"), + context.build_runtime_call( + runtime_api::INPUT, &[input_pointer_casted.into(), length_pointer_casted.into()], - "call_seal_input", - )?; + ); // Store the calldata size let calldata_size = context @@ -218,8 +218,9 @@ where 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")?; + for symbol in runtime_api::EXPORTS { + context.declare_extern_function(symbol)?; + } Ok(()) } @@ -240,7 +241,7 @@ where true, ); - context.set_current_function("deploy")?; + context.set_current_function(runtime_api::DEPLOY)?; context.set_basic_block(context.current_function().borrow().entry_block()); assert!(context @@ -250,7 +251,7 @@ where context.set_basic_block(context.current_function().borrow().return_block); context.build_unreachable(); - context.set_current_function("call")?; + context.set_current_function(runtime_api::CALL)?; context.set_basic_block(context.current_function().borrow().entry_block()); assert!(context diff --git a/crates/llvm-context/src/polkavm/context/mod.rs b/crates/llvm-context/src/polkavm/context/mod.rs index 4800edc..fc44483 100644 --- a/crates/llvm-context/src/polkavm/context/mod.rs +++ b/crates/llvm-context/src/polkavm/context/mod.rs @@ -27,6 +27,7 @@ use inkwell::values::BasicValue; use crate::optimizer::settings::Settings as OptimizerSettings; use crate::optimizer::Optimizer; +use crate::polkavm::r#const::*; use crate::polkavm::DebugConfig; use crate::polkavm::Dependency; use crate::target_machine::target::Target; @@ -36,6 +37,7 @@ use self::address_space::AddressSpace; use self::attribute::Attribute; use self::build::Build; use self::code_type::CodeType; +// TODO // use self::debug_info::DebugInfo; use self::evmla_data::EVMLAData; use self::function::declaration::Declaration as FunctionDeclaration; @@ -137,25 +139,21 @@ where .link_in_module(pallet_contracts_pvm_llapi::module(llvm, "polkavm_guest").unwrap()) .expect("the PolkaVM guest API module should be linkable"); - let call_function = module.get_function("call").unwrap(); - call_function.add_attribute( - inkwell::attributes::AttributeLoc::Function, - llvm.create_enum_attribute(Attribute::NoReturn as u32, 0), - ); - assert!(call_function.get_first_basic_block().is_none()); + for export in runtime_api::EXPORTS { + module + .get_function(export) + .expect("should be declared") + .add_attribute( + inkwell::attributes::AttributeLoc::Function, + llvm.create_enum_attribute(Attribute::NoReturn as u32, 0), + ); + } - let deploy_function = module.get_function("deploy").unwrap(); - deploy_function.add_attribute( - inkwell::attributes::AttributeLoc::Function, - llvm.create_enum_attribute(Attribute::NoReturn as u32, 0), - ); - assert!(deploy_function.get_first_basic_block().is_none()); - - // TODO: Factor out a list - // Also should be prefixed with double underscores - for name in ["seal_return", "input", "set_storage", "get_storage"] { - let runtime_api_function = module.get_function(name).expect("should be declared"); - runtime_api_function.set_linkage(inkwell::module::Linkage::External); + for import in runtime_api::IMPORTS { + module + .get_function(import) + .expect("should be declared") + .set_linkage(inkwell::module::Linkage::External); } } @@ -335,6 +333,13 @@ where self.code_type.to_owned() } + /// Returns the function value of a runtime API method. + pub fn runtime_api_method(&self, name: &'static str) -> inkwell::values::FunctionValue<'ctx> { + self.module() + .get_function(name) + .unwrap_or_else(|| panic!("runtime API method {name} not declared")) + } + /// Returns the pointer to a global variable. pub fn get_global(&self, name: &str) -> anyhow::Result> { match self.globals.get(name) { @@ -675,23 +680,19 @@ where self.integer_const(32, revive_common::BIT_LENGTH_FIELD as u64), )?; - let runtime_api = self - .module() - .get_function("get_storage") - .expect("should be declared"); - let arguments = &[ - storage_key_pointer_casted.into(), - self.integer_const(32, 32).into(), - storage_value_pointer_casted.into(), - storage_value_length_pointer_casted.into(), - ]; - let _ = self - .builder() - .build_call(runtime_api, arguments, "call_seal_get_storage")? - .try_as_basic_value() - .left() - .expect("should not be a void function type"); - // TODO check return value + self.build_runtime_call( + runtime_api::GET_STORAGE, + &[ + storage_key_pointer_casted.into(), + self.integer_const(32, 32).into(), + storage_value_pointer_casted.into(), + storage_value_length_pointer_casted.into(), + ], + ); + + // We do not to check the return value. + // Solidity assumes infallible SLOAD. + // If a key doesn't exist the "zero" value is returned. self.build_load(storage_value_pointer, "storage_value_load") .map(|value| self.build_byte_swap(value)) @@ -753,8 +754,6 @@ where } AddressSpace::TransientStorage => todo!(), AddressSpace::Storage => { - // TODO: Tests, factor out into dedicated functions - let storage_key_value = self.builder().build_ptr_to_int( pointer.value, self.field_type(), @@ -785,18 +784,15 @@ where self.builder() .build_store(storage_value_pointer.value, storage_value_value)?; - let runtime_api = self - .module() - .get_function("set_storage") - .expect("should be declared"); - let arguments = &[ - storage_key_pointer_casted.into(), - self.integer_const(32, 32).into(), - storage_value_pointer_casted.into(), - self.integer_const(32, 32).into(), - ]; - self.builder() - .build_call(runtime_api, arguments, "call_seal_set_storage")?; + self.build_runtime_call( + runtime_api::SET_STORAGE, + &[ + storage_key_pointer_casted.into(), + self.integer_const(32, 32).into(), + storage_value_pointer_casted.into(), + self.integer_const(32, 32).into(), + ], + ); } AddressSpace::Code | AddressSpace::HeapAuxiliary => {} AddressSpace::Generic => self.build_store( @@ -878,6 +874,27 @@ where .unwrap(); } + /// Builds a call to a runtime API method. + pub fn build_runtime_call( + &self, + name: &'static str, + arguments: &[inkwell::values::BasicValueEnum<'ctx>], + ) -> Option> { + self.builder + .build_direct_call( + self.runtime_api_method(name), + &arguments + .iter() + .copied() + .map(inkwell::values::BasicMetadataValueEnum::from) + .collect::>(), + &format!("runtime API call {name}"), + ) + .unwrap() + .try_as_basic_value() + .left() + } + /// Builds a call. pub fn build_call( &self, @@ -1118,11 +1135,10 @@ where "return_data_ptr_to_int", )?; - self.builder().build_call( - self.module().get_function("seal_return").unwrap(), + self.build_runtime_call( + runtime_api::RETURN, &[flags.into(), offset_pointer.into(), length_pointer.into()], - "seal_return", - )?; + ); self.build_unreachable(); Ok(()) @@ -1173,7 +1189,7 @@ where Ok(self .builder() .build_call( - self.module().get_function("__sbrk").expect("is declared"), + self.runtime_api_method(runtime_api::SBRK), &[size.into()], "call_sbrk", )? diff --git a/crates/llvm-context/src/polkavm/evm/crypto.rs b/crates/llvm-context/src/polkavm/evm/crypto.rs index ae7abd5..9c18c23 100644 --- a/crates/llvm-context/src/polkavm/evm/crypto.rs +++ b/crates/llvm-context/src/polkavm/evm/crypto.rs @@ -2,6 +2,7 @@ use crate::polkavm::context::Context; use crate::polkavm::Dependency; +use crate::polkavm_const::runtime_api; /// Translates the `sha3` instruction. pub fn sha3<'ctx, D>( @@ -28,20 +29,14 @@ where "output_pointer_casted", )?; - let function = context - .module() - .get_function("hash_keccak_256") - .expect("is declared"); - - context.builder().build_call( - function, + context.build_runtime_call( + runtime_api::HASH_KECCAK_256, &[ 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")?)) } diff --git a/crates/llvm-context/src/polkavm/evm/ether_gas.rs b/crates/llvm-context/src/polkavm/evm/ether_gas.rs index 5624de7..ae1860f 100644 --- a/crates/llvm-context/src/polkavm/evm/ether_gas.rs +++ b/crates/llvm-context/src/polkavm/evm/ether_gas.rs @@ -4,6 +4,7 @@ use inkwell::values::BasicValue; use crate::polkavm::context::Context; use crate::polkavm::Dependency; +use crate::polkavm_const::runtime_api; /// Translates the `gas` instruction. pub fn gas<'ctx, D>( @@ -43,17 +44,13 @@ where ), )?; - context.builder().build_call( - context - .module() - .get_function("value_transferred") - .expect("is declared"), + context.build_runtime_call( + runtime_api::VALUE_TRANSFERRED, &[ 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(