From a07968205be55351e8aa18b2e762c585ea01df6e Mon Sep 17 00:00:00 2001 From: xermicus Date: Tue, 25 Feb 2025 16:47:01 +0100 Subject: [PATCH] llvm-context: modularize compiler builtin functions (#234) - Add the revive runtime function interface to minimize boiler plate code. - Outline heavily repeated code into dedicated functions to bring down code size. - The code size tests builds optimized for size. - Function attributes are passed as slices. This significantly brings down the code size for all OpenZeppelin wizard contracts (using all possible features) compiled against OpenZeppelin `v5.0.0` with size optimizations. |contract|| `-Oz` main | `-Oz` PR || `-O3` main | `-O3` PR | |-|-|-|-|-|-|-| |erc1155.sol||100K|67K||114K|147K| |erc20.sol||120K|90K||160K|191K| |erc721.sol||128K|101K||178K|214K| |governor.sol||226K|165K||293K|349K| |rwa.sol||116K|85K||154K|185K| |stable.sol||116K|86K||155K|192K| On the flip side this introduces a heavy penalty for cycle optimized builds. Setting the no-inline attributes for cycle optimized builds helps a lot but heavily penalizes runtime speed (LLVM does not yet inline everything properly - to be investigated later on). Next steps: - Modularize more functions - Refactor the YUL function arguments to use pointers instead of values - Afterwards check if LLVM still has trouble inline-ing properly on O3 or set the no-inline attribute if it does not penalize runtime performance too bad. --- CHANGELOG.md | 11 +- Cargo.lock | 2 + crates/integration/Cargo.toml | 1 + crates/integration/codesize.json | 16 +- crates/integration/src/cases.rs | 62 +++- crates/linker/src/lib.rs | 1 - crates/llvm-context/src/lib.rs | 16 +- .../polkavm/context/function/llvm_runtime.rs | 2 +- .../src/polkavm/context/function/mod.rs | 57 ++-- .../context/function/runtime/arithmetics.rs | 269 ++++++++++++++++++ .../polkavm/context/function/runtime/entry.rs | 2 +- .../function/runtime/immutable_data_load.rs | 118 -------- .../polkavm/context/function/runtime/mod.rs | 6 +- .../context/function/runtime/revive.rs | 147 ++++++++++ .../llvm-context/src/polkavm/context/mod.rs | 243 ++++------------ .../src/polkavm/context/pointer/heap.rs | 110 +++++++ .../context/{pointer.rs => pointer/mod.rs} | 3 + .../src/polkavm/context/pointer/storage.rs | 135 +++++++++ .../src/polkavm/context/runtime.rs | 113 ++++++++ .../src/polkavm/evm/arithmetic.rs | 132 ++------- crates/llvm-context/src/polkavm/evm/event.rs | 235 ++++++++++----- .../llvm-context/src/polkavm/evm/immutable.rs | 203 ++++++++++++- crates/llvm-context/src/polkavm/evm/memory.rs | 11 +- crates/llvm-context/src/polkavm/evm/return.rs | 62 +--- .../llvm-context/src/polkavm/evm/storage.rs | 49 ++-- crates/runner/Cargo.toml | 1 + crates/runner/src/lib.rs | 1 + crates/runtime-api/src/polkavm_imports.c | 6 +- crates/solidity/src/test_utils.rs | 14 +- .../statement/expression/function_call/mod.rs | 22 +- .../parser/statement/function_definition.rs | 2 +- .../src/yul/parser/statement/object.rs | 47 ++- 32 files changed, 1444 insertions(+), 655 deletions(-) create mode 100644 crates/llvm-context/src/polkavm/context/function/runtime/arithmetics.rs delete mode 100644 crates/llvm-context/src/polkavm/context/function/runtime/immutable_data_load.rs create mode 100644 crates/llvm-context/src/polkavm/context/function/runtime/revive.rs create mode 100644 crates/llvm-context/src/polkavm/context/pointer/heap.rs rename crates/llvm-context/src/polkavm/context/{pointer.rs => pointer/mod.rs} (99%) create mode 100644 crates/llvm-context/src/polkavm/context/pointer/storage.rs create mode 100644 crates/llvm-context/src/polkavm/context/runtime.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index b5eef16f..7b25e197 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,18 @@ ## Unreleased +## v0.1.0-dev.12 + This is a development pre-release. -Supported `polkadot-sdk` rev: `274a781e8ca1a9432c7ec87593bd93214abbff50` +Supported `polkadot-sdk` rev: `21f6f0705e53c15aa2b8a5706b208200447774a9` + +### Added + +### Changed +- Improved code size: Large contracts compile to smaller code blobs using with size optimization. + +### Fixed ## v0.1.0-dev.11 diff --git a/Cargo.lock b/Cargo.lock index 7b162f9c..92085c82 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8317,6 +8317,7 @@ dependencies = [ "alloy-sol-types", "hex", "rayon", + "revive-llvm-context", "revive-runner", "revive-solidity", "serde", @@ -8386,6 +8387,7 @@ dependencies = [ "parity-scale-codec", "polkadot-sdk 0.1.0", "revive-differential", + "revive-llvm-context", "revive-solidity", "scale-info", "serde", diff --git a/crates/integration/Cargo.toml b/crates/integration/Cargo.toml index 53e89027..e6483c5f 100644 --- a/crates/integration/Cargo.toml +++ b/crates/integration/Cargo.toml @@ -15,6 +15,7 @@ serde_json = { workspace = true } revive-solidity = { workspace = true } revive-runner = { workspace = true } +revive-llvm-context = { workspace = true } [dev-dependencies] sha1 = { workspace = true } diff --git a/crates/integration/codesize.json b/crates/integration/codesize.json index 7d8d00f1..c0a31750 100644 --- a/crates/integration/codesize.json +++ b/crates/integration/codesize.json @@ -1,10 +1,10 @@ { - "Baseline": 1237, - "Computation": 3119, - "DivisionArithmetics": 16561, - "ERC20": 23966, - "Events": 2102, - "FibonacciIterative": 2521, - "Flipper": 2745, - "SHA1": 17004 + "Baseline": 1443, + "Computation": 2788, + "DivisionArithmetics": 9748, + "ERC20": 19203, + "Events": 2201, + "FibonacciIterative": 2041, + "Flipper": 2632, + "SHA1": 8958 } \ No newline at end of file diff --git a/crates/integration/src/cases.rs b/crates/integration/src/cases.rs index 179122d6..41eae483 100644 --- a/crates/integration/src/cases.rs +++ b/crates/integration/src/cases.rs @@ -1,6 +1,7 @@ use alloy_primitives::{Address, Bytes, I256, U256}; use alloy_sol_types::{sol, SolCall, SolConstructor}; +use revive_llvm_context::OptimizerSettings; use revive_solidity::test_utils::*; #[derive(Clone)] @@ -250,7 +251,7 @@ sol!( case!("Storage.sol", Storage, transientCall, storage_transient, value: U256); impl Contract { - fn build(calldata: Vec, name: &'static str, code: &str) -> Self { + pub fn build(calldata: Vec, name: &'static str, code: &str) -> Self { Self { name, evm_runtime: compile_evm_bin_runtime(name, code), @@ -258,11 +259,19 @@ impl Contract { calldata, } } + + pub fn build_size_opt(calldata: Vec, name: &'static str, code: &str) -> Self { + Self { + name, + evm_runtime: compile_evm_bin_runtime(name, code), + pvm_runtime: compile_blob_with_options(name, code, true, OptimizerSettings::size()), + calldata, + } + } } #[cfg(test)] mod tests { - use alloy_primitives::{Bytes, U256}; use rayon::iter::{IntoParallelIterator, ParallelIterator}; use serde::{de::Deserialize, Serialize}; use std::{collections::BTreeMap, fs::File}; @@ -302,14 +311,47 @@ mod tests { }; [ - Contract::baseline as fn() -> Contract, - Contract::flipper as fn() -> Contract, - (|| Contract::odd_product(0)) as fn() -> Contract, - (|| Contract::fib_iterative(U256::ZERO)) as fn() -> Contract, - Contract::erc20 as fn() -> Contract, - (|| Contract::sha1(Bytes::new())) as fn() -> Contract, - (|| Contract::division_arithmetics_div(U256::ZERO, U256::ZERO)) as fn() -> Contract, - (|| Contract::event(U256::ZERO)) as fn() -> Contract, + (|| { + Contract::build_size_opt( + vec![], + "Baseline", + include_str!("../contracts/Baseline.sol"), + ) + }) as _, + (|| { + Contract::build_size_opt( + vec![], + "Flipper", + include_str!("../contracts/flipper.sol"), + ) + }) as _, + (|| { + Contract::build_size_opt( + vec![], + "Computation", + include_str!("../contracts/Computation.sol"), + ) + }) as _, + (|| { + Contract::build_size_opt( + vec![], + "FibonacciIterative", + include_str!("../contracts/Fibonacci.sol"), + ) + }) as _, + (|| Contract::build_size_opt(vec![], "ERC20", include_str!("../contracts/ERC20.sol"))) + as _, + (|| Contract::build_size_opt(vec![], "SHA1", include_str!("../contracts/SHA1.sol"))) + as _, + (|| { + Contract::build_size_opt( + vec![], + "DivisionArithmetics", + include_str!("../contracts/DivisionArithmetics.sol"), + ) + }) as _, + (|| Contract::build_size_opt(vec![], "Events", include_str!("../contracts/Events.sol"))) + as _, ] .into_par_iter() .map(extract_code_size) diff --git a/crates/linker/src/lib.rs b/crates/linker/src/lib.rs index ba92450f..9ded0268 100644 --- a/crates/linker/src/lib.rs +++ b/crates/linker/src/lib.rs @@ -52,7 +52,6 @@ pub fn link>(input: T) -> anyhow::Result> { let ld_args = [ "ld.lld", - "--lto=full", "--error-limit=0", "--relocatable", "--emit-relocs", diff --git a/crates/llvm-context/src/lib.rs b/crates/llvm-context/src/lib.rs index e37ed682..279ad4bf 100644 --- a/crates/llvm-context/src/lib.rs +++ b/crates/llvm-context/src/lib.rs @@ -21,17 +21,25 @@ pub use self::polkavm::context::function::declaration::Declaration as PolkaVMFun pub use self::polkavm::context::function::intrinsics::Intrinsics as PolkaVMIntrinsicFunction; pub use self::polkavm::context::function::llvm_runtime::LLVMRuntime as PolkaVMLLVMRuntime; pub use self::polkavm::context::function::r#return::Return as PolkaVMFunctionReturn; +pub use self::polkavm::context::function::runtime::arithmetics::Division as PolkaVMDivisionFunction; +pub use self::polkavm::context::function::runtime::arithmetics::Remainder as PolkaVMRemainderFunction; +pub use self::polkavm::context::function::runtime::arithmetics::SignedDivision as PolkaVMSignedDivisionFunction; +pub use self::polkavm::context::function::runtime::arithmetics::SignedRemainder as PolkaVMSignedRemainderFunction; pub use self::polkavm::context::function::runtime::deploy_code::DeployCode as PolkaVMDeployCodeFunction; pub use self::polkavm::context::function::runtime::entry::Entry as PolkaVMEntryFunction; -pub use self::polkavm::context::function::runtime::immutable_data_load::ImmutableDataLoad as PolkaVMImmutableDataLoadFunction; +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::FUNCTION_DEPLOY_CODE as PolkaVMFunctionDeployCode; pub use self::polkavm::context::function::runtime::FUNCTION_ENTRY as PolkaVMFunctionEntry; -pub use self::polkavm::context::function::runtime::FUNCTION_LOAD_IMMUTABLE_DATA as PolkaVMFunctionImmutableDataLoad; pub use self::polkavm::context::function::runtime::FUNCTION_RUNTIME_CODE as PolkaVMFunctionRuntimeCode; pub use self::polkavm::context::function::yul_data::YulData as PolkaVMFunctionYulData; pub use self::polkavm::context::function::Function as PolkaVMFunction; pub use self::polkavm::context::global::Global as PolkaVMGlobal; +pub use self::polkavm::context::pointer::heap::LoadWord as PolkaVMLoadHeapWordFunction; +pub use self::polkavm::context::pointer::heap::StoreWord as PolkaVMStoreHeapWordFunction; +pub use self::polkavm::context::pointer::storage::LoadWord as PolkaVMLoadStorageWordFunction; +pub use self::polkavm::context::pointer::storage::StoreWord as PolkaVMStoreStorageWordFunction; pub use self::polkavm::context::pointer::Pointer as PolkaVMPointer; pub use self::polkavm::context::r#loop::Loop as PolkaVMLoop; pub use self::polkavm::context::solidity_data::SolidityData as PolkaVMContextSolidityData; @@ -47,8 +55,12 @@ pub use self::polkavm::evm::create as polkavm_evm_create; pub use self::polkavm::evm::crypto as polkavm_evm_crypto; pub use self::polkavm::evm::ether_gas as polkavm_evm_ether_gas; pub use self::polkavm::evm::event as polkavm_evm_event; +pub use self::polkavm::evm::event::EventLog as PolkaVMEventLogFunction; 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; diff --git a/crates/llvm-context/src/polkavm/context/function/llvm_runtime.rs b/crates/llvm-context/src/polkavm/context/function/llvm_runtime.rs index fc5c05ea..92d0b826 100644 --- a/crates/llvm-context/src/polkavm/context/function/llvm_runtime.rs +++ b/crates/llvm-context/src/polkavm/context/function/llvm_runtime.rs @@ -91,7 +91,7 @@ impl<'ctx> LLVMRuntime<'ctx> { llvm, sha3, //vec![Attribute::ArgMemOnly, Attribute::ReadOnly], - vec![], + &[], false, ); diff --git a/crates/llvm-context/src/polkavm/context/function/mod.rs b/crates/llvm-context/src/polkavm/context/function/mod.rs index 8308116a..15a32229 100644 --- a/crates/llvm-context/src/polkavm/context/function/mod.rs +++ b/crates/llvm-context/src/polkavm/context/function/mod.rs @@ -81,8 +81,7 @@ impl<'ctx> Function<'ctx> { || (name.starts_with("__") && name != self::runtime::FUNCTION_ENTRY && name != self::runtime::FUNCTION_DEPLOY_CODE - && name != self::runtime::FUNCTION_RUNTIME_CODE - && name != self::runtime::FUNCTION_LOAD_IMMUTABLE_DATA) + && name != self::runtime::FUNCTION_RUNTIME_CODE) } /// Returns the LLVM function declaration. @@ -110,30 +109,21 @@ impl<'ctx> Function<'ctx> { pub fn set_attributes( llvm: &'ctx inkwell::context::Context, declaration: Declaration<'ctx>, - attributes: Vec, + attributes: &[Attribute], force: bool, ) { - for attribute_kind in attributes.into_iter() { + for attribute_kind in attributes { match attribute_kind { Attribute::Memory => unimplemented!("`memory` attributes are not 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), - ); - } + 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( @@ -142,12 +132,12 @@ impl<'ctx> Function<'ctx> { ); declaration.value.add_attribute( inkwell::attributes::AttributeLoc::Function, - llvm.create_enum_attribute(attribute_kind as u32, 0), + 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), + llvm.create_enum_attribute(*attribute_kind as u32, 0), ), } } @@ -178,27 +168,16 @@ impl<'ctx> Function<'ctx> { 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 { + if optimizer.settings().level_middle_end_size == SizeLevel::Z { Self::set_attributes( llvm, declaration, - vec![Attribute::OptimizeForSize, Attribute::MinSize], + &[Attribute::OptimizeForSize, Attribute::MinSize], false, ); } - Self::set_attributes(llvm, declaration, vec![Attribute::NoFree], false); + Self::set_attributes(llvm, declaration, &[Attribute::NoFree], false); } /// Sets the front-end runtime attributes. @@ -208,7 +187,7 @@ impl<'ctx> Function<'ctx> { optimizer: &Optimizer, ) { if optimizer.settings().level_middle_end_size == SizeLevel::Z { - Self::set_attributes(llvm, declaration, vec![Attribute::NoInline], false); + Self::set_attributes(llvm, declaration, &[Attribute::NoInline], false); } } @@ -220,7 +199,7 @@ impl<'ctx> Function<'ctx> { Self::set_attributes( llvm, declaration, - vec![ + &[ Attribute::MustProgress, Attribute::NoUnwind, Attribute::WillReturn, diff --git a/crates/llvm-context/src/polkavm/context/function/runtime/arithmetics.rs b/crates/llvm-context/src/polkavm/context/function/runtime/arithmetics.rs new file mode 100644 index 00000000..ba9395f8 --- /dev/null +++ b/crates/llvm-context/src/polkavm/context/function/runtime/arithmetics.rs @@ -0,0 +1,269 @@ +//! Translates the arithmetic operations. + +use inkwell::values::BasicValue; + +use crate::polkavm::context::runtime::RuntimeFunction; +use crate::polkavm::context::Context; +use crate::polkavm::Dependency; +use crate::polkavm::WriteLLVM; + +/// Implements the division operator according to the EVM specification. +pub struct Division; + +impl RuntimeFunction for Division +where + D: Dependency + Clone, +{ + const NAME: &'static str = "__revive_division"; + + fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> { + context.word_type().fn_type( + &[context.word_type().into(), context.word_type().into()], + false, + ) + } + + fn emit_body<'ctx>( + &self, + context: &mut Context<'ctx, D>, + ) -> anyhow::Result>> { + let operand_1 = Self::paramater(context, 0).into_int_value(); + let operand_2 = Self::paramater(context, 1).into_int_value(); + + wrapped_division(context, operand_2, || { + Ok(context + .builder() + .build_int_unsigned_div(operand_1, operand_2, "DIV")?) + }) + .map(Into::into) + } +} + +impl WriteLLVM for Division +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) + } +} + +/// Implements the signed division operator according to the EVM specification. +pub struct SignedDivision; + +impl RuntimeFunction for SignedDivision +where + D: Dependency + Clone, +{ + const NAME: &'static str = "__revive_signed_division"; + + fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> { + context.word_type().fn_type( + &[context.word_type().into(), context.word_type().into()], + false, + ) + } + + fn emit_body<'ctx>( + &self, + context: &mut Context<'ctx, D>, + ) -> anyhow::Result>> { + let operand_1 = Self::paramater(context, 0).into_int_value(); + let operand_2 = Self::paramater(context, 1).into_int_value(); + + let block_calculate = context.append_basic_block("calculate"); + let block_overflow = context.append_basic_block("overflow"); + let block_select = context.append_basic_block("select_result"); + let block_origin = context.basic_block(); + context.builder().build_switch( + operand_2, + block_calculate, + &[ + (context.word_type().const_zero(), block_select), + (context.word_type().const_all_ones(), block_overflow), + ], + )?; + + context.set_basic_block(block_calculate); + let quotient = context + .builder() + .build_int_signed_div(operand_1, operand_2, "SDIV")?; + context.build_unconditional_branch(block_select); + + context.set_basic_block(block_overflow); + let max_uint = context.builder().build_int_z_extend( + context + .integer_type(revive_common::BIT_LENGTH_WORD - 1) + .const_all_ones(), + context.word_type(), + "max_uint", + )?; + let is_operand_1_overflow = context.builder().build_int_compare( + inkwell::IntPredicate::EQ, + operand_1, + context.builder().build_int_neg(max_uint, "min_uint")?, + "is_operand_1_overflow", + )?; + context.build_conditional_branch(is_operand_1_overflow, block_select, block_calculate)?; + + context.set_basic_block(block_select); + let result = context.builder().build_phi(context.word_type(), "result")?; + result.add_incoming(&[ + (&operand_1, block_overflow), + (&context.word_const(0), block_origin), + ("ient.as_basic_value_enum(), block_calculate), + ]); + Ok(Some(result.as_basic_value())) + } +} + +impl WriteLLVM for SignedDivision +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) + } +} + +/// Implements the remainder operator according to the EVM specification. +pub struct Remainder; + +impl RuntimeFunction for Remainder +where + D: Dependency + Clone, +{ + const NAME: &'static str = "__revive_remainder"; + + fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> { + context.word_type().fn_type( + &[context.word_type().into(), context.word_type().into()], + false, + ) + } + + fn emit_body<'ctx>( + &self, + context: &mut Context<'ctx, D>, + ) -> anyhow::Result>> { + let operand_1 = Self::paramater(context, 0).into_int_value(); + let operand_2 = Self::paramater(context, 1).into_int_value(); + + wrapped_division(context, operand_2, || { + Ok(context + .builder() + .build_int_unsigned_rem(operand_1, operand_2, "MOD")?) + }) + .map(Into::into) + } +} + +impl WriteLLVM for Remainder +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) + } +} + +/// Implements the signed remainder operator according to the EVM specification. +pub struct SignedRemainder; + +impl RuntimeFunction for SignedRemainder +where + D: Dependency + Clone, +{ + const NAME: &'static str = "__revive_signed_remainder"; + + fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> { + context.word_type().fn_type( + &[context.word_type().into(), context.word_type().into()], + false, + ) + } + + fn emit_body<'ctx>( + &self, + context: &mut Context<'ctx, D>, + ) -> anyhow::Result>> { + let operand_1 = Self::paramater(context, 0).into_int_value(); + let operand_2 = Self::paramater(context, 1).into_int_value(); + + wrapped_division(context, operand_2, || { + Ok(context + .builder() + .build_int_signed_rem(operand_1, operand_2, "SMOD")?) + }) + .map(Into::into) + } +} + +impl WriteLLVM for SignedRemainder +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) + } +} + +/// Wrap division operations so that zero will be returned if the +/// denominator is zero (see also Ethereum YP Appendix H.2). +/// +/// The closure is expected to calculate and return the quotient. +/// +/// The result is either the calculated quotient or zero, +/// selected at runtime. +fn wrapped_division<'ctx, D, F, T>( + context: &Context<'ctx, D>, + denominator: inkwell::values::IntValue<'ctx>, + f: F, +) -> anyhow::Result> +where + D: Dependency + Clone, + F: FnOnce() -> anyhow::Result, + T: inkwell::values::IntMathValue<'ctx>, +{ + assert_eq!( + denominator.get_type().get_bit_width(), + revive_common::BIT_LENGTH_WORD as u32 + ); + + let block_calculate = context.append_basic_block("calculate"); + let block_select = context.append_basic_block("select"); + let block_origin = context.basic_block(); + context.builder().build_switch( + denominator, + block_calculate, + &[(context.word_const(0), block_select)], + )?; + + context.set_basic_block(block_calculate); + let calculated_value = f()?.as_basic_value_enum(); + context.build_unconditional_branch(block_select); + + context.set_basic_block(block_select); + let result = context.builder().build_phi(context.word_type(), "result")?; + result.add_incoming(&[ + (&context.word_const(0), block_origin), + (&calculated_value, block_calculate), + ]); + Ok(result.as_basic_value()) +} 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 cb0f332a..2154ecaa 100644 --- a/crates/llvm-context/src/polkavm/context/function/runtime/entry.rs +++ b/crates/llvm-context/src/polkavm/context/function/runtime/entry.rs @@ -145,7 +145,7 @@ where crate::PolkaVMFunction::set_attributes( context.llvm(), entry, - vec![crate::PolkaVMAttribute::NoReturn], + &[crate::PolkaVMAttribute::NoReturn], true, ); diff --git a/crates/llvm-context/src/polkavm/context/function/runtime/immutable_data_load.rs b/crates/llvm-context/src/polkavm/context/function/runtime/immutable_data_load.rs deleted file mode 100644 index 108e076b..00000000 --- a/crates/llvm-context/src/polkavm/context/function/runtime/immutable_data_load.rs +++ /dev/null @@ -1,118 +0,0 @@ -//! The immutable data runtime function. - -use crate::polkavm::context::address_space::AddressSpace; -use crate::polkavm::context::function::runtime; -use crate::polkavm::context::pointer::Pointer; -use crate::polkavm::context::Context; -use crate::polkavm::Dependency; -use crate::polkavm::WriteLLVM; - -/// A function for requesting the immutable data from the runtime. -/// This is a special function that is only used by the front-end generated code. -/// -/// The runtime API is called lazily and subsequent calls are no-ops. -/// -/// The bytes written is asserted to match the expected length. -/// This should never fail; the length is known. -/// However, this is a one time assertion, hence worth it. -#[derive(Debug)] -pub struct ImmutableDataLoad; - -impl WriteLLVM for ImmutableDataLoad -where - D: Dependency + Clone, -{ - fn declare(&mut self, context: &mut Context) -> anyhow::Result<()> { - context.add_function( - runtime::FUNCTION_LOAD_IMMUTABLE_DATA, - context.void_type().fn_type(Default::default(), false), - 0, - Some(inkwell::module::Linkage::External), - )?; - - Ok(()) - } - - fn into_llvm(self, context: &mut Context) -> anyhow::Result<()> { - context.set_current_function(runtime::FUNCTION_LOAD_IMMUTABLE_DATA, None)?; - context.set_basic_block(context.current_function().borrow().entry_block()); - - let immutable_data_size_pointer = context - .get_global(revive_runtime_api::immutable_data::GLOBAL_IMMUTABLE_DATA_SIZE)? - .value - .as_pointer_value(); - let immutable_data_size = context.build_load( - Pointer::new( - context.xlen_type(), - AddressSpace::Stack, - immutable_data_size_pointer, - ), - "immutable_data_size_load", - )?; - - let load_immutable_data_block = context.append_basic_block("load_immutables_block"); - let return_block = context.current_function().borrow().return_block(); - let immutable_data_size_is_zero = context.builder().build_int_compare( - inkwell::IntPredicate::EQ, - context.xlen_type().const_zero(), - immutable_data_size.into_int_value(), - "immutable_data_size_is_zero", - )?; - context.build_conditional_branch( - immutable_data_size_is_zero, - return_block, - load_immutable_data_block, - )?; - - context.set_basic_block(load_immutable_data_block); - let output_pointer = context - .get_global(revive_runtime_api::immutable_data::GLOBAL_IMMUTABLE_DATA_POINTER)? - .value - .as_pointer_value(); - context.build_runtime_call( - revive_runtime_api::polkavm_imports::GET_IMMUTABLE_DATA, - &[ - context - .builder() - .build_ptr_to_int(output_pointer, context.xlen_type(), "ptr_to_xlen")? - .into(), - context - .builder() - .build_ptr_to_int( - immutable_data_size_pointer, - context.xlen_type(), - "ptr_to_xlen", - )? - .into(), - ], - ); - let bytes_written = context.builder().build_load( - context.xlen_type(), - immutable_data_size_pointer, - "bytes_written", - )?; - context.builder().build_store( - immutable_data_size_pointer, - context.xlen_type().const_zero(), - )?; - let overflow_block = context.append_basic_block("immutable_data_overflow"); - let is_overflow = context.builder().build_int_compare( - inkwell::IntPredicate::UGT, - immutable_data_size.into_int_value(), - bytes_written.into_int_value(), - "is_overflow", - )?; - context.build_conditional_branch(is_overflow, overflow_block, return_block)?; - - context.set_basic_block(overflow_block); - context.build_call(context.intrinsics().trap, &[], "invalid_trap"); - context.build_unreachable(); - - context.set_basic_block(return_block); - context.build_return(None); - - context.pop_debug_scope(); - - Ok(()) - } -} 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 0712f31a..3b79973b 100644 --- a/crates/llvm-context/src/polkavm/context/function/runtime/mod.rs +++ b/crates/llvm-context/src/polkavm/context/function/runtime/mod.rs @@ -1,8 +1,9 @@ //! The front-end runtime functions. +pub mod arithmetics; pub mod deploy_code; pub mod entry; -pub mod immutable_data_load; +pub mod revive; pub mod runtime_code; /// The main entry function name. @@ -13,6 +14,3 @@ pub const FUNCTION_DEPLOY_CODE: &str = "__deploy"; /// The runtime code function name. pub const FUNCTION_RUNTIME_CODE: &str = "__runtime"; - -/// The immutable data load function name. -pub const FUNCTION_LOAD_IMMUTABLE_DATA: &str = "__immutable_data_load"; diff --git a/crates/llvm-context/src/polkavm/context/function/runtime/revive.rs b/crates/llvm-context/src/polkavm/context/function/runtime/revive.rs new file mode 100644 index 00000000..294186c0 --- /dev/null +++ b/crates/llvm-context/src/polkavm/context/function/runtime/revive.rs @@ -0,0 +1,147 @@ +//! The revive compiler runtime functions. + +use inkwell::values::BasicValue; + +use crate::polkavm::context::function::Attribute; +use crate::polkavm::context::runtime::RuntimeFunction; +use crate::polkavm::context::Context; +use crate::polkavm::Dependency; +use crate::polkavm::WriteLLVM; + +/// Pointers are represented as opaque 256 bit integer values in EVM. +/// In practice, they should never exceed a register sized bit value. +/// However, we still protect against this possibility here: Heap index +/// offsets are generally untrusted and potentially represent valid +/// (but wrong) pointers when truncated. +pub struct WordToPointer; + +impl RuntimeFunction for WordToPointer +where + D: Dependency + Clone, +{ + const NAME: &'static str = "__revive_int_truncate"; + + const ATTRIBUTES: &'static [Attribute] = &[ + Attribute::WillReturn, + Attribute::NoFree, + Attribute::AlwaysInline, + ]; + + fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> { + context + .xlen_type() + .fn_type(&[context.word_type().into()], false) + } + + fn emit_body<'ctx>( + &self, + context: &mut Context<'ctx, D>, + ) -> anyhow::Result>> { + let value = Self::paramater(context, 0).into_int_value(); + let truncated = + context + .builder() + .build_int_truncate(value, context.xlen_type(), "offset_truncated")?; + let extended = context.builder().build_int_z_extend( + truncated, + context.word_type(), + "offset_extended", + )?; + let is_overflow = context.builder().build_int_compare( + inkwell::IntPredicate::NE, + value, + extended, + "compare_truncated_extended", + )?; + + let block_continue = context.append_basic_block("offset_pointer_ok"); + let block_trap = context.append_basic_block("offset_pointer_overflow"); + context.build_conditional_branch(is_overflow, block_trap, block_continue)?; + + context.set_basic_block(block_trap); + context.build_call(context.intrinsics().trap, &[], "invalid_trap"); + context.build_unreachable(); + + context.set_basic_block(block_continue); + Ok(Some(truncated.as_basic_value_enum())) + } +} + +impl WriteLLVM for WordToPointer +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) + } +} + +/// The revive runtime exit function. +pub struct Exit; + +impl RuntimeFunction for Exit +where + D: Dependency + Clone, +{ + const NAME: &'static str = "__revive_exit"; + + const ATTRIBUTES: &'static [Attribute] = &[ + Attribute::NoReturn, + Attribute::NoFree, + Attribute::AlwaysInline, + ]; + + fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> { + context.void_type().fn_type( + &[ + context.xlen_type().into(), + context.word_type().into(), + context.word_type().into(), + ], + false, + ) + } + + fn emit_body<'ctx>( + &self, + context: &mut Context<'ctx, D>, + ) -> anyhow::Result>> { + let flags = Self::paramater(context, 0).into_int_value(); + let offset = Self::paramater(context, 1).into_int_value(); + let length = Self::paramater(context, 2).into_int_value(); + + let offset_truncated = context.safe_truncate_int_to_xlen(offset)?; + let length_truncated = context.safe_truncate_int_to_xlen(length)?; + let heap_pointer = context.build_heap_gep(offset_truncated, length_truncated)?; + let offset_pointer = context.builder().build_ptr_to_int( + heap_pointer.value, + context.xlen_type(), + "return_data_ptr_to_int", + )?; + + context.build_runtime_call( + revive_runtime_api::polkavm_imports::RETURN, + &[flags.into(), offset_pointer.into(), length_truncated.into()], + ); + context.build_unreachable(); + + Ok(None) + } +} + +impl WriteLLVM for Exit +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 94046d01..6558fe29 100644 --- a/crates/llvm-context/src/polkavm/context/mod.rs +++ b/crates/llvm-context/src/polkavm/context/mod.rs @@ -10,6 +10,7 @@ pub mod function; pub mod global; pub mod r#loop; pub mod pointer; +pub mod runtime; pub mod solidity_data; pub mod yul_data; @@ -31,6 +32,8 @@ use crate::polkavm::DebugConfig; use crate::polkavm::Dependency; use crate::target_machine::target::Target; use crate::target_machine::TargetMachine; +use crate::PolkaVMLoadHeapWordFunction; +use crate::PolkaVMStoreHeapWordFunction; use self::address_space::AddressSpace; use self::attribute::Attribute; @@ -41,10 +44,13 @@ use self::function::declaration::Declaration as FunctionDeclaration; use self::function::intrinsics::Intrinsics; use self::function::llvm_runtime::LLVMRuntime; use self::function::r#return::Return as FunctionReturn; +use self::function::runtime::revive::Exit; +use self::function::runtime::revive::WordToPointer; use self::function::Function; use self::global::Global; use self::pointer::Pointer; use self::r#loop::Loop; +use self::runtime::RuntimeFunction; use self::solidity_data::SolidityData; use self::yul_data::YulData; @@ -721,6 +727,7 @@ where name: &str, ) -> Pointer<'ctx> { let pointer = self.builder.build_alloca(r#type, name).unwrap(); + pointer .as_instruction() .unwrap() @@ -768,60 +775,21 @@ where ) -> anyhow::Result> { match pointer.address_space { AddressSpace::Heap => { - let heap_pointer = self.build_heap_gep( - self.builder().build_ptr_to_int( - pointer.value, - self.xlen_type(), - "offset_ptrtoint", - )?, - pointer - .r#type - .size_of() - .expect("should be IntValue") - .const_truncate(self.xlen_type()), - )?; - - let value = self + let name = >::NAME; + let declaration = + >::declaration(self); + let arguments = [self .builder() - .build_load(pointer.r#type, heap_pointer.value, name)?; - self.basic_block() - .get_last_instruction() - .expect("Always exists") - .set_alignment(revive_common::BYTE_LENGTH_BYTE as u32) - .expect("Alignment is valid"); - - self.build_byte_swap(value) + .build_ptr_to_int(pointer.value, self.xlen_type(), "offset_ptrtoint")? + .as_basic_value_enum()]; + Ok(self + .build_call(declaration, &arguments, "heap_load") + .unwrap_or_else(|| { + panic!("revive runtime function {name} should return a value") + })) } AddressSpace::Storage | AddressSpace::TransientStorage => { - let storage_value_pointer = - self.build_alloca(self.word_type(), "storage_value_pointer"); - self.build_store(storage_value_pointer, self.word_const(0))?; - - let storage_value_length_pointer = - self.build_alloca(self.xlen_type(), "storage_value_length_pointer"); - self.build_store( - storage_value_length_pointer, - self.word_const(revive_common::BIT_LENGTH_WORD as u64), - )?; - - let transient = pointer.address_space == AddressSpace::TransientStorage; - - self.build_runtime_call( - revive_runtime_api::polkavm_imports::GET_STORAGE, - &[ - self.xlen_type().const_int(transient as u64, false).into(), - pointer.to_int(self).into(), - self.xlen_type().const_all_ones().into(), - storage_value_pointer.to_int(self).into(), - storage_value_length_pointer.to_int(self).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") + unreachable!("should use the runtime function") } AddressSpace::Stack => { let value = self @@ -847,60 +815,16 @@ where { match pointer.address_space { AddressSpace::Heap => { - let heap_pointer = self.build_heap_gep( - self.builder().build_ptr_to_int( - pointer.value, - self.xlen_type(), - "offset_ptrtoint", - )?, - value - .as_basic_value_enum() - .get_type() - .size_of() - .expect("should be IntValue") - .const_truncate(self.xlen_type()), - )?; - - let value = value.as_basic_value_enum(); - let value = match value.get_type().into_int_type().get_bit_width() as usize { - revive_common::BIT_LENGTH_WORD => self.build_byte_swap(value)?, - revive_common::BIT_LENGTH_BYTE => value, - _ => unreachable!("Only word and byte sized values can be stored on EVM heap"), - }; - - self.builder - .build_store(heap_pointer.value, value)? - .set_alignment(revive_common::BYTE_LENGTH_BYTE as u32) - .expect("Alignment is valid"); + let declaration = + >::declaration(self); + let arguments = [ + pointer.to_int(self).as_basic_value_enum(), + value.as_basic_value_enum(), + ]; + self.build_call(declaration, &arguments, "heap_store"); } AddressSpace::Storage | AddressSpace::TransientStorage => { - assert_eq!( - value.as_basic_value_enum().get_type(), - self.word_type().as_basic_type_enum() - ); - - let storage_value_pointer = self.build_alloca(self.word_type(), "storage_value"); - let storage_value_pointer_casted = self.builder().build_ptr_to_int( - storage_value_pointer.value, - self.xlen_type(), - "storage_value_pointer_casted", - )?; - - self.builder() - .build_store(storage_value_pointer.value, value)?; - - let transient = pointer.address_space == AddressSpace::TransientStorage; - - self.build_runtime_call( - revive_runtime_api::polkavm_imports::SET_STORAGE, - &[ - self.xlen_type().const_int(transient as u64, false).into(), - pointer.to_int(self).into(), - self.xlen_type().const_all_ones().into(), - storage_value_pointer_casted.into(), - self.integer_const(crate::polkavm::XLEN, 32).into(), - ], - ); + unreachable!("should use the runtime function") } AddressSpace::Stack => { let instruction = self.builder.build_store(pointer.value, value).unwrap(); @@ -1115,38 +1039,16 @@ where offset: inkwell::values::IntValue<'ctx>, length: inkwell::values::IntValue<'ctx>, ) -> anyhow::Result<()> { - let offset_truncated = self.safe_truncate_int_to_xlen(offset)?; - let length_truncated = self.safe_truncate_int_to_xlen(length)?; - let offset_into_heap = self.build_heap_gep(offset_truncated, length_truncated)?; - - let length_pointer = self.safe_truncate_int_to_xlen(length)?; - let offset_pointer = self.builder().build_ptr_to_int( - offset_into_heap.value, - self.xlen_type(), - "return_data_ptr_to_int", - )?; - - self.build_runtime_call( - revive_runtime_api::polkavm_imports::RETURN, - &[flags.into(), offset_pointer.into(), length_pointer.into()], + self.build_call( + >::declaration(self), + &[flags.into(), offset.into(), length.into()], + "exit", ); - self.build_unreachable(); Ok(()) } /// Truncate a memory offset to register size, trapping if it doesn't fit. - /// Pointers are represented as opaque 256 bit integer values in EVM. - /// In practice, they should never exceed a register sized bit value. - /// However, we still protect against this possibility here. Heap index - /// offsets are generally untrusted and potentially represent valid - /// (but wrong) pointers when truncated. - /// - /// TODO: Splitting up into a dedicated function - /// could potentially decrease code sizes (LLVM can still decide to inline). - /// However, passing i256 parameters is counter productive and - /// I've found that splitting it up actualy increases code size. - /// Should be reviewed after 64bit support. pub fn safe_truncate_int_to_xlen( &self, value: inkwell::values::IntValue<'ctx>, @@ -1160,29 +1062,19 @@ where "expected XLEN or WORD sized int type for memory offset", ); - let truncated = - self.builder() - .build_int_truncate(value, self.xlen_type(), "offset_truncated")?; - let extended = - self.builder() - .build_int_z_extend(truncated, self.word_type(), "offset_extended")?; - let is_overflow = self.builder().build_int_compare( - inkwell::IntPredicate::NE, - value, - extended, - "compare_truncated_extended", - )?; - - let block_continue = self.append_basic_block("offset_pointer_ok"); - let block_trap = self.append_basic_block("offset_pointer_overflow"); - self.build_conditional_branch(is_overflow, block_trap, block_continue)?; - - self.set_basic_block(block_trap); - self.build_call(self.intrinsics().trap, &[], "invalid_trap"); - self.build_unreachable(); - - self.set_basic_block(block_continue); - Ok(truncated) + Ok(self + .build_call( + >::declaration(self), + &[value.into()], + "word_to_pointer", + ) + .unwrap_or_else(|| { + panic!( + "revive runtime function {} should return a value", + >::NAME, + ) + }) + .into_int_value()) } /// Build a call to PolkaVM `sbrk` for extending the heap from offset by `size`. @@ -1222,34 +1114,6 @@ where Ok(memory_size_value.into_int_value()) } - /// Call PolkaVM `sbrk` for extending the heap by `offset` + `size`, - /// trapping the contract if the call failed. - pub fn build_heap_alloc( - &self, - offset: inkwell::values::IntValue<'ctx>, - size: inkwell::values::IntValue<'ctx>, - ) -> anyhow::Result<()> { - let end_of_memory = self.build_sbrk(offset, size)?; - let return_is_nil = self.builder().build_int_compare( - inkwell::IntPredicate::EQ, - end_of_memory, - self.llvm().ptr_type(Default::default()).const_null(), - "compare_end_of_memory_nil", - )?; - - let continue_block = self.append_basic_block("sbrk_not_nil"); - let trap_block = self.append_basic_block("sbrk_nil"); - self.build_conditional_branch(return_is_nil, trap_block, continue_block)?; - - self.set_basic_block(trap_block); - self.build_call(self.intrinsics().trap, &[], "invalid_trap"); - self.build_unreachable(); - - self.set_basic_block(continue_block); - - Ok(()) - } - /// Returns a pointer to `offset` into the heap, allocating /// enough memory if `offset + length` would be out of bounds. /// # Panics @@ -1262,19 +1126,8 @@ where assert_eq!(offset.get_type(), self.xlen_type()); assert_eq!(length.get_type(), self.xlen_type()); - self.build_heap_alloc(offset, length)?; - - let heap_start = self - .module() - .get_global(revive_runtime_api::polkavm_imports::MEMORY) - .expect("the memory symbol should have been declared") - .as_pointer_value(); - Ok(self.build_gep( - Pointer::new(self.byte_type(), AddressSpace::Stack, heap_start), - &[offset], - self.byte_type(), - "heap_offset_via_gep", - )) + let pointer = self.build_sbrk(offset, length)?; + Ok(Pointer::new(self.byte_type(), AddressSpace::Stack, pointer)) } /// Returns a boolean type constant. @@ -1580,4 +1433,8 @@ where anyhow::bail!("The immutable size data is not available"); } } + + pub fn optimizer_settings(&self) -> &OptimizerSettings { + self.optimizer.settings() + } } diff --git a/crates/llvm-context/src/polkavm/context/pointer/heap.rs b/crates/llvm-context/src/polkavm/context/pointer/heap.rs new file mode 100644 index 00000000..a143e897 --- /dev/null +++ b/crates/llvm-context/src/polkavm/context/pointer/heap.rs @@ -0,0 +1,110 @@ +//! The revive simulated EVM linear memory pointer functions. + +use inkwell::values::BasicValueEnum; + +use crate::polkavm::context::runtime::RuntimeFunction; +use crate::polkavm::context::Context; +use crate::polkavm::Dependency; +use crate::polkavm::WriteLLVM; + +/// Load a word size value from a heap pointer. +pub struct LoadWord; + +impl RuntimeFunction for LoadWord +where + D: Dependency + Clone, +{ + const NAME: &'static str = "__revive_load_heap_word"; + + fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> { + context + .word_type() + .fn_type(&[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 length = context + .xlen_type() + .const_int(revive_common::BYTE_LENGTH_WORD as u64, false); + let pointer = context.build_heap_gep(offset, length)?; + let value = context + .builder() + .build_load(context.word_type(), pointer.value, "value")?; + context + .basic_block() + .get_last_instruction() + .expect("Always exists") + .set_alignment(revive_common::BYTE_LENGTH_BYTE as u32) + .expect("Alignment is valid"); + + let swapped_value = context.build_byte_swap(value)?; + Ok(Some(swapped_value)) + } +} + +impl WriteLLVM for LoadWord +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) + } +} + +/// Store a word size value through a heap pointer. +pub struct StoreWord; + +impl RuntimeFunction for StoreWord +where + D: Dependency + Clone, +{ + const NAME: &'static str = "__revive_store_heap_word"; + + fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> { + context.void_type().fn_type( + &[context.xlen_type().into(), context.word_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 length = context + .xlen_type() + .const_int(revive_common::BYTE_LENGTH_WORD as u64, false); + let pointer = context.build_heap_gep(offset, length)?; + + let value = context.build_byte_swap(Self::paramater(context, 1))?; + + context + .builder() + .build_store(pointer.value, value)? + .set_alignment(revive_common::BYTE_LENGTH_BYTE as u32) + .expect("Alignment is valid"); + Ok(None) + } +} + +impl WriteLLVM for StoreWord +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/pointer.rs b/crates/llvm-context/src/polkavm/context/pointer/mod.rs similarity index 99% rename from crates/llvm-context/src/polkavm/context/pointer.rs rename to crates/llvm-context/src/polkavm/context/pointer/mod.rs index 64431427..afd1e5e8 100644 --- a/crates/llvm-context/src/polkavm/context/pointer.rs +++ b/crates/llvm-context/src/polkavm/context/pointer/mod.rs @@ -7,6 +7,9 @@ use crate::polkavm::context::global::Global; use crate::polkavm::context::Context; use crate::polkavm::Dependency; +pub mod heap; +pub mod storage; + /// The LLVM pointer. #[derive(Debug, Clone, Copy)] pub struct Pointer<'ctx> { diff --git a/crates/llvm-context/src/polkavm/context/pointer/storage.rs b/crates/llvm-context/src/polkavm/context/pointer/storage.rs new file mode 100644 index 00000000..0f9cdf65 --- /dev/null +++ b/crates/llvm-context/src/polkavm/context/pointer/storage.rs @@ -0,0 +1,135 @@ +//! The revive storage pointer functions. + +use inkwell::values::BasicValueEnum; + +use crate::polkavm::context::runtime::RuntimeFunction; +use crate::polkavm::context::Context; +use crate::polkavm::Dependency; +use crate::polkavm::WriteLLVM; + +/// Load a word size value from a storage pointer. +pub struct LoadWord; + +impl RuntimeFunction for LoadWord +where + D: Dependency + Clone, +{ + const NAME: &'static str = "__revive_load_storage_word"; + + fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> { + context.word_type().fn_type( + &[context.xlen_type().into(), context.word_type().into()], + false, + ) + } + + fn emit_body<'ctx>( + &self, + context: &mut Context<'ctx, D>, + ) -> anyhow::Result>> { + let is_transient = Self::paramater(context, 0); + let key_value = Self::paramater(context, 1); + + let key_pointer = context.build_alloca_at_entry(context.word_type(), "key_pointer"); + let value_pointer = context.build_alloca_at_entry(context.word_type(), "value_pointer"); + let length_pointer = context.build_alloca_at_entry(context.xlen_type(), "length_pointer"); + + context + .builder() + .build_store(key_pointer.value, key_value)?; + context.build_store(value_pointer, context.word_const(0))?; + context.build_store( + length_pointer, + context + .xlen_type() + .const_int(revive_common::BYTE_LENGTH_WORD as u64, false), + )?; + + let arguments = [ + is_transient, + key_pointer.to_int(context).into(), + context.xlen_type().const_all_ones().into(), + value_pointer.to_int(context).into(), + length_pointer.to_int(context).into(), + ]; + context.build_runtime_call(revive_runtime_api::polkavm_imports::GET_STORAGE, &arguments); + + // We do not to check the return value: Solidity assumes infallible loads. + // If a key doesn't exist the "zero" value is returned (ensured by above write). + + Ok(Some(context.build_load(value_pointer, "storage_value")?)) + } +} + +impl WriteLLVM for LoadWord +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) + } +} + +/// Store a word size value through a storage pointer. +pub struct StoreWord; + +impl RuntimeFunction for StoreWord +where + D: Dependency + Clone, +{ + const NAME: &'static str = "__revive_store_storage_word"; + + fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> { + context.void_type().fn_type( + &[ + context.xlen_type().into(), + context.word_type().into(), + context.word_type().into(), + ], + false, + ) + } + + fn emit_body<'ctx>( + &self, + context: &mut Context<'ctx, D>, + ) -> anyhow::Result>> { + let is_transient = Self::paramater(context, 0); + let key = Self::paramater(context, 1); + let value = Self::paramater(context, 2); + + let key_pointer = context.build_alloca_at_entry(context.word_type(), "key_pointer"); + let value_pointer = context.build_alloca_at_entry(context.word_type(), "value_pointer"); + + context.build_store(key_pointer, key)?; + context.build_store(value_pointer, value)?; + + let arguments = [ + is_transient, + key_pointer.to_int(context).into(), + context.xlen_type().const_all_ones().into(), + value_pointer.to_int(context).into(), + context.integer_const(crate::polkavm::XLEN, 32).into(), + ]; + context.build_runtime_call(revive_runtime_api::polkavm_imports::SET_STORAGE, &arguments); + + Ok(None) + } +} + +impl WriteLLVM for StoreWord +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/runtime.rs b/crates/llvm-context/src/polkavm/context/runtime.rs new file mode 100644 index 00000000..1617eb05 --- /dev/null +++ b/crates/llvm-context/src/polkavm/context/runtime.rs @@ -0,0 +1,113 @@ +//! The revive compiler runtime function interface definition. +//! +//! Common routines should not be inlined but extracted into smaller functions. +//! This benefits contract code size. + +use crate::optimizer::settings::size_level::SizeLevel; +use crate::polkavm::context::function::declaration::Declaration; +use crate::polkavm::context::function::Function; +use crate::polkavm::context::Attribute; +use crate::polkavm::context::Context; +use crate::polkavm::Dependency; + +/// The revive runtime function interface simplifies declaring runtime functions +/// and code emitting by providing helpful default implementations. +pub trait RuntimeFunction +where + D: Dependency + Clone, +{ + /// The function name. + const NAME: &'static str; + + const ATTRIBUTES: &'static [Attribute] = &[ + Attribute::NoFree, + Attribute::NoRecurse, + Attribute::WillReturn, + ]; + + /// The function type. + fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx>; + + /// Declare the function. + fn declare(&self, context: &mut Context) -> anyhow::Result<()> { + let function = context.add_function( + Self::NAME, + Self::r#type(context), + 0, + Some(inkwell::module::Linkage::External), + )?; + + let mut attributes = Self::ATTRIBUTES.to_vec(); + attributes.extend_from_slice(match context.optimizer_settings().level_middle_end_size { + SizeLevel::Zero => &[], + _ => &[Attribute::OptimizeForSize, Attribute::MinSize], + }); + Function::set_attributes( + context.llvm(), + function.borrow().declaration(), + &attributes, + true, + ); + + Ok(()) + } + + /// Get the function declaration. + fn declaration<'ctx>(context: &Context<'ctx, D>) -> Declaration<'ctx> { + context + .get_function(Self::NAME) + .unwrap_or_else(|| panic!("runtime function {} should be declared", Self::NAME)) + .borrow() + .declaration() + } + + /// Emit the function. + fn emit(&self, context: &mut Context) -> anyhow::Result<()> { + context.set_current_function(Self::NAME, None)?; + context.set_basic_block(context.current_function().borrow().entry_block()); + + let return_value = self.emit_body(context)?; + self.emit_epilogue(context, return_value); + + context.pop_debug_scope(); + + Ok(()) + } + + /// Emit the function body. + fn emit_body<'ctx>( + &self, + context: &mut Context<'ctx, D>, + ) -> anyhow::Result>>; + + /// Emit the function return instructions. + fn emit_epilogue<'ctx>( + &self, + context: &mut Context<'ctx, D>, + return_value: Option>, + ) { + let return_block = context.current_function().borrow().return_block(); + context.build_unconditional_branch(return_block); + context.set_basic_block(return_block); + match return_value { + Some(value) => context.build_return(Some(&value)), + None => context.build_return(None), + } + } + + /// Get the nth function paramater. + fn paramater<'ctx>( + context: &Context<'ctx, D>, + index: usize, + ) -> inkwell::values::BasicValueEnum<'ctx> { + let name = Self::NAME; + context + .get_function(name) + .unwrap_or_else(|| panic!("runtime function {name} should be declared")) + .borrow() + .declaration() + .function_value() + .get_nth_param(index as u32) + .unwrap_or_else(|| panic!("runtime function {name} should have parameter #{index}")) + } +} diff --git a/crates/llvm-context/src/polkavm/evm/arithmetic.rs b/crates/llvm-context/src/polkavm/evm/arithmetic.rs index 49aaaab4..143cf1c8 100644 --- a/crates/llvm-context/src/polkavm/evm/arithmetic.rs +++ b/crates/llvm-context/src/polkavm/evm/arithmetic.rs @@ -2,8 +2,13 @@ use inkwell::values::BasicValue; +use crate::polkavm::context::runtime::RuntimeFunction; use crate::polkavm::context::Context; use crate::polkavm::Dependency; +use crate::PolkaVMDivisionFunction; +use crate::PolkaVMRemainderFunction; +use crate::PolkaVMSignedDivisionFunction; +use crate::PolkaVMSignedRemainderFunction; /// Translates the arithmetic addition. pub fn addition<'ctx, D>( @@ -59,11 +64,11 @@ pub fn division<'ctx, D>( where D: Dependency + Clone, { - wrapped_division(context, operand_2, || { - Ok(context - .builder() - .build_int_unsigned_div(operand_1, operand_2, "DIV")?) - }) + let name = >::NAME; + let declaration = >::declaration(context); + Ok(context + .build_call(declaration, &[operand_1.into(), operand_2.into()], "div") + .unwrap_or_else(|| panic!("revive runtime function {name} should return a value",))) } /// Translates the arithmetic remainder. @@ -75,11 +80,11 @@ pub fn remainder<'ctx, D>( where D: Dependency + Clone, { - wrapped_division(context, operand_2, || { - Ok(context - .builder() - .build_int_unsigned_rem(operand_1, operand_2, "MOD")?) - }) + let name = >::NAME; + let declaration = >::declaration(context); + Ok(context + .build_call(declaration, &[operand_1.into(), operand_2.into()], "rem") + .unwrap_or_else(|| panic!("revive runtime function {name} should return a value",))) } /// Translates the signed arithmetic division. @@ -94,54 +99,11 @@ pub fn division_signed<'ctx, D>( where D: Dependency + Clone, { - assert_eq!( - operand_2.get_type().get_bit_width(), - revive_common::BIT_LENGTH_WORD as u32 - ); - - let block_calculate = context.append_basic_block("calculate"); - let block_overflow = context.append_basic_block("overflow"); - let block_select = context.append_basic_block("select_result"); - let block_origin = context.basic_block(); - context.builder().build_switch( - operand_2, - block_calculate, - &[ - (context.word_type().const_zero(), block_select), - (context.word_type().const_all_ones(), block_overflow), - ], - )?; - - context.set_basic_block(block_calculate); - let quotient = context - .builder() - .build_int_signed_div(operand_1, operand_2, "SDIV")?; - context.build_unconditional_branch(block_select); - - context.set_basic_block(block_overflow); - let max_uint = context.builder().build_int_z_extend( - context - .integer_type(revive_common::BIT_LENGTH_WORD - 1) - .const_all_ones(), - context.word_type(), - "max_uint", - )?; - let is_operand_1_overflow = context.builder().build_int_compare( - inkwell::IntPredicate::EQ, - operand_1, - context.builder().build_int_neg(max_uint, "min_uint")?, - "is_operand_1_overflow", - )?; - context.build_conditional_branch(is_operand_1_overflow, block_select, block_calculate)?; - - context.set_basic_block(block_select); - let result = context.builder().build_phi(context.word_type(), "result")?; - result.add_incoming(&[ - (&operand_1, block_overflow), - (&context.word_const(0), block_origin), - ("ient.as_basic_value_enum(), block_calculate), - ]); - Ok(result.as_basic_value()) + let name = >::NAME; + let declaration = >::declaration(context); + Ok(context + .build_call(declaration, &[operand_1.into(), operand_2.into()], "sdiv") + .unwrap_or_else(|| panic!("revive runtime function {name} should return a value",))) } /// Translates the signed arithmetic remainder. @@ -153,53 +115,9 @@ pub fn remainder_signed<'ctx, D>( where D: Dependency + Clone, { - wrapped_division(context, operand_2, || { - Ok(context - .builder() - .build_int_signed_rem(operand_1, operand_2, "SMOD")?) - }) -} - -/// Wrap division operations so that zero will be returned if the -/// denominator is zero (see also Ethereum YP Appendix H.2). -/// -/// The closure is expected to calculate and return the quotient. -/// -/// The result is either the calculated quotient or zero, -/// selected at runtime. -fn wrapped_division<'ctx, D, F, T>( - context: &Context<'ctx, D>, - denominator: inkwell::values::IntValue<'ctx>, - f: F, -) -> anyhow::Result> -where - D: Dependency + Clone, - F: FnOnce() -> anyhow::Result, - T: inkwell::values::IntMathValue<'ctx>, -{ - assert_eq!( - denominator.get_type().get_bit_width(), - revive_common::BIT_LENGTH_WORD as u32 - ); - - let block_calculate = context.append_basic_block("calculate"); - let block_select = context.append_basic_block("select"); - let block_origin = context.basic_block(); - context.builder().build_switch( - denominator, - block_calculate, - &[(context.word_const(0), block_select)], - )?; - - context.set_basic_block(block_calculate); - let calculated_value = f()?.as_basic_value_enum(); - context.build_unconditional_branch(block_select); - - context.set_basic_block(block_select); - let result = context.builder().build_phi(context.word_type(), "result")?; - result.add_incoming(&[ - (&context.word_const(0), block_origin), - (&calculated_value, block_calculate), - ]); - Ok(result.as_basic_value()) + let name = >::NAME; + let declaration = >::declaration(context); + Ok(context + .build_call(declaration, &[operand_1.into(), operand_2.into()], "srem") + .unwrap_or_else(|| panic!("revive runtime function {name} should return a value",))) } diff --git a/crates/llvm-context/src/polkavm/evm/event.rs b/crates/llvm-context/src/polkavm/evm/event.rs index 2c10c90d..ba7fc9ef 100644 --- a/crates/llvm-context/src/polkavm/evm/event.rs +++ b/crates/llvm-context/src/polkavm/evm/event.rs @@ -2,84 +2,183 @@ use inkwell::values::BasicValue; +use crate::polkavm::context::runtime::RuntimeFunction; use crate::polkavm::context::Context; use crate::polkavm::Dependency; +use crate::polkavm::WriteLLVM; + +/// A function for emitting EVM event logs from contract code. +pub struct EventLog; + +impl RuntimeFunction for EventLog +where + D: Dependency + Clone, +{ + const NAME: &'static str = match N { + 0 => "__revive_log_0", + 1 => "__revive_log_1", + 2 => "__revive_log_2", + 3 => "__revive_log_3", + 4 => "__revive_log_4", + _ => unreachable!(), + }; + + fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> { + let mut parameter_types = vec![context.xlen_type().into(), context.xlen_type().into()]; + parameter_types.extend_from_slice(&[context.word_type().into(); N]); + context.void_type().fn_type(¶meter_types, false) + } + + fn emit_body<'ctx>( + &self, + context: &mut Context<'ctx, D>, + ) -> anyhow::Result>> { + let input_offset = Self::paramater(context, 0).into_int_value(); + let input_length = Self::paramater(context, 1).into_int_value(); + let input_pointer = context.builder().build_ptr_to_int( + context.build_heap_gep(input_offset, input_length)?.value, + context.xlen_type(), + "event_input_offset", + )?; + + let arguments = if N == 0 { + [ + context.xlen_type().const_zero().as_basic_value_enum(), + context.xlen_type().const_zero().as_basic_value_enum(), + input_pointer.as_basic_value_enum(), + input_length.as_basic_value_enum(), + ] + } else { + let topics_buffer_size = N * revive_common::BYTE_LENGTH_WORD; + let topics_buffer_pointer = context.build_alloca_at_entry( + context.byte_type().array_type(topics_buffer_size as u32), + "topics_buffer", + ); + + for n in 0..N { + let topic = Self::paramater(context, n + 2); + let topic_buffer_offset = context + .xlen_type() + .const_int((n * revive_common::BYTE_LENGTH_WORD) as u64, false); + context.build_store( + context.build_gep( + topics_buffer_pointer, + &[context.xlen_type().const_zero(), topic_buffer_offset], + context.byte_type(), + &format!("topic_buffer_{N}_gep"), + ), + context.build_byte_swap(topic.as_basic_value_enum())?, + )?; + } + + [ + context + .builder() + .build_ptr_to_int( + topics_buffer_pointer.value, + context.xlen_type(), + "event_topics_offset", + )? + .as_basic_value_enum(), + context + .xlen_type() + .const_int(N as u64, false) + .as_basic_value_enum(), + input_pointer.as_basic_value_enum(), + input_length.as_basic_value_enum(), + ] + }; + + context.build_runtime_call( + revive_runtime_api::polkavm_imports::DEPOSIT_EVENT, + &arguments, + ); + + Ok(None) + } +} + +impl WriteLLVM for EventLog<0> +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) + } +} + +impl WriteLLVM for EventLog<1> +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) + } +} + +impl WriteLLVM for EventLog<2> +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) + } +} + +impl WriteLLVM for EventLog<3> +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) + } +} + +impl WriteLLVM for EventLog<4> +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) + } +} /// Translates a log or event call. -/// -/// TODO: Splitting up into dedicated functions (log0..log4) -/// could potentially decrease code sizes (LLVM can still decide to inline). -/// However, passing i256 parameters is counter productive and -/// I've found that splitting it up actualy increases code size. -/// Should be reviewed after 64bit support. -pub fn log<'ctx, D>( +pub fn log<'ctx, D, const N: usize>( context: &mut Context<'ctx, D>, input_offset: inkwell::values::IntValue<'ctx>, input_length: inkwell::values::IntValue<'ctx>, - topics: Vec>, + topics: [inkwell::values::BasicValueEnum<'ctx>; N], ) -> anyhow::Result<()> where D: Dependency + Clone, { - let input_offset = context.safe_truncate_int_to_xlen(input_offset)?; - let input_length = context.safe_truncate_int_to_xlen(input_length)?; - let input_pointer = context.builder().build_ptr_to_int( - context.build_heap_gep(input_offset, input_length)?.value, - context.xlen_type(), - "event_input_offset", - )?; - - let arguments = if topics.is_empty() { - [ - context.xlen_type().const_zero().as_basic_value_enum(), - context.xlen_type().const_zero().as_basic_value_enum(), - input_pointer.as_basic_value_enum(), - input_length.as_basic_value_enum(), - ] - } else { - let topics_buffer_size = topics.len() * revive_common::BYTE_LENGTH_WORD; - let topics_buffer_pointer = context.build_alloca( - context.byte_type().array_type(topics_buffer_size as u32), - "topics_buffer", - ); - - for (n, topic) in topics.iter().enumerate() { - let topic_buffer_offset = context - .xlen_type() - .const_int((n * revive_common::BYTE_LENGTH_WORD) as u64, false); - context.build_store( - context.build_gep( - topics_buffer_pointer, - &[context.xlen_type().const_zero(), topic_buffer_offset], - context.byte_type(), - "topic_buffer_gep", - ), - context.build_byte_swap(topic.as_basic_value_enum())?, - )?; - } - - [ - context - .builder() - .build_ptr_to_int( - topics_buffer_pointer.value, - context.xlen_type(), - "event_topics_offset", - )? - .as_basic_value_enum(), - context - .xlen_type() - .const_int(topics.len() as u64, false) - .as_basic_value_enum(), - input_pointer.as_basic_value_enum(), - input_length.as_basic_value_enum(), - ] - }; - - let _ = context.build_runtime_call( - revive_runtime_api::polkavm_imports::DEPOSIT_EVENT, - &arguments, - ); - + let declaration = as RuntimeFunction>::declaration(context); + let mut arguments = vec![ + context.safe_truncate_int_to_xlen(input_offset)?.into(), + context.safe_truncate_int_to_xlen(input_length)?.into(), + ]; + arguments.extend_from_slice(&topics); + context.build_call(declaration, &arguments, "log"); Ok(()) } diff --git a/crates/llvm-context/src/polkavm/evm/immutable.rs b/crates/llvm-context/src/polkavm/evm/immutable.rs index f1f72cf9..60f28276 100644 --- a/crates/llvm-context/src/polkavm/evm/immutable.rs +++ b/crates/llvm-context/src/polkavm/evm/immutable.rs @@ -4,10 +4,206 @@ use inkwell::types::BasicType; use crate::polkavm::context::address_space::AddressSpace; use crate::polkavm::context::code_type::CodeType; -use crate::polkavm::context::function::runtime; use crate::polkavm::context::pointer::Pointer; +use crate::polkavm::context::runtime::RuntimeFunction; use crate::polkavm::context::Context; use crate::polkavm::Dependency; +use crate::polkavm::WriteLLVM; + +/// A function for requesting the immutable data from the runtime. +/// This is a special function that is only used by the front-end generated code. +/// +/// The runtime API is called lazily and subsequent calls are no-ops. +/// +/// The bytes written is asserted to match the expected length. +/// This should never fail; the length is known. +/// However, this is a one time assertion, hence worth it. +pub struct Load; + +impl RuntimeFunction for Load +where + D: Dependency + Clone, +{ + const NAME: &'static str = "__revive_load_immutable_data"; + + fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> { + context.void_type().fn_type(Default::default(), false) + } + + fn emit_body<'ctx>( + &self, + context: &mut Context<'ctx, D>, + ) -> anyhow::Result>> { + let immutable_data_size_pointer = context + .get_global(revive_runtime_api::immutable_data::GLOBAL_IMMUTABLE_DATA_SIZE)? + .value + .as_pointer_value(); + let immutable_data_size = context.build_load( + Pointer::new( + context.xlen_type(), + AddressSpace::Stack, + immutable_data_size_pointer, + ), + "immutable_data_size_load", + )?; + + let load_immutable_data_block = context.append_basic_block("load_immutables_block"); + let return_block = context.current_function().borrow().return_block(); + let immutable_data_size_is_zero = context.builder().build_int_compare( + inkwell::IntPredicate::EQ, + context.xlen_type().const_zero(), + immutable_data_size.into_int_value(), + "immutable_data_size_is_zero", + )?; + context.build_conditional_branch( + immutable_data_size_is_zero, + return_block, + load_immutable_data_block, + )?; + + context.set_basic_block(load_immutable_data_block); + let output_pointer = context + .get_global(revive_runtime_api::immutable_data::GLOBAL_IMMUTABLE_DATA_POINTER)? + .value + .as_pointer_value(); + context.build_runtime_call( + revive_runtime_api::polkavm_imports::GET_IMMUTABLE_DATA, + &[ + context + .builder() + .build_ptr_to_int(output_pointer, context.xlen_type(), "ptr_to_xlen")? + .into(), + context + .builder() + .build_ptr_to_int( + immutable_data_size_pointer, + context.xlen_type(), + "ptr_to_xlen", + )? + .into(), + ], + ); + let bytes_written = context.builder().build_load( + context.xlen_type(), + immutable_data_size_pointer, + "bytes_written", + )?; + context.builder().build_store( + immutable_data_size_pointer, + context.xlen_type().const_zero(), + )?; + let overflow_block = context.append_basic_block("immutable_data_overflow"); + let is_overflow = context.builder().build_int_compare( + inkwell::IntPredicate::UGT, + immutable_data_size.into_int_value(), + bytes_written.into_int_value(), + "is_overflow", + )?; + context.build_conditional_branch(is_overflow, overflow_block, return_block)?; + + context.set_basic_block(overflow_block); + context.build_call(context.intrinsics().trap, &[], "invalid_trap"); + context.build_unreachable(); + + Ok(None) + } +} + +impl WriteLLVM for Load +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) + } +} + +/// Store the immutable data from the constructor code. +pub struct Store; + +impl RuntimeFunction for Store +where + D: Dependency + Clone, +{ + const NAME: &'static str = "__revive_store_immutable_data"; + + fn r#type<'ctx>(context: &Context<'ctx, D>) -> inkwell::types::FunctionType<'ctx> { + context.void_type().fn_type(Default::default(), false) + } + + fn emit_body<'ctx>( + &self, + context: &mut Context<'ctx, D>, + ) -> anyhow::Result>> { + let immutable_data_size_pointer = context + .get_global(revive_runtime_api::immutable_data::GLOBAL_IMMUTABLE_DATA_SIZE)? + .value + .as_pointer_value(); + let immutable_data_size = context.build_load( + Pointer::new( + context.xlen_type(), + AddressSpace::Stack, + immutable_data_size_pointer, + ), + "immutable_data_size_load", + )?; + + let write_immutable_data_block = context.append_basic_block("write_immutables_block"); + let join_return_block = context.append_basic_block("join_return_block"); + let immutable_data_size_is_zero = context.builder().build_int_compare( + inkwell::IntPredicate::EQ, + context.xlen_type().const_zero(), + immutable_data_size.into_int_value(), + "immutable_data_size_is_zero", + )?; + context.build_conditional_branch( + immutable_data_size_is_zero, + join_return_block, + write_immutable_data_block, + )?; + + context.set_basic_block(write_immutable_data_block); + let immutable_data_pointer = context + .get_global(revive_runtime_api::immutable_data::GLOBAL_IMMUTABLE_DATA_POINTER)? + .value + .as_pointer_value(); + context.build_runtime_call( + revive_runtime_api::polkavm_imports::SET_IMMUTABLE_DATA, + &[ + context + .builder() + .build_ptr_to_int( + immutable_data_pointer, + context.xlen_type(), + "immutable_data_pointer_to_xlen", + )? + .into(), + immutable_data_size, + ], + ); + context.build_unconditional_branch(join_return_block); + + context.set_basic_block(join_return_block); + Ok(None) + } +} + +impl WriteLLVM for Store +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) + } +} /// Translates the contract immutable load. /// @@ -27,14 +223,15 @@ where } Some(CodeType::Deploy) => load_from_memory(context, index), Some(CodeType::Runtime) => { + let name = >::NAME; context.build_call( context - .get_function(runtime::FUNCTION_LOAD_IMMUTABLE_DATA) + .get_function(name) .expect("is always declared for runtime code") .borrow() .declaration(), &[], - runtime::FUNCTION_LOAD_IMMUTABLE_DATA, + name, ); load_from_memory(context, index) } diff --git a/crates/llvm-context/src/polkavm/evm/memory.rs b/crates/llvm-context/src/polkavm/evm/memory.rs index 8db2db83..9182f08b 100644 --- a/crates/llvm-context/src/polkavm/evm/memory.rs +++ b/crates/llvm-context/src/polkavm/evm/memory.rs @@ -85,5 +85,14 @@ where offset, "mstore8_destination", ); - context.build_store(pointer, value) + let pointer = context.build_sbrk( + pointer.to_int(context), + context.xlen_type().const_int(1, false), + )?; + context + .builder() + .build_store(pointer, value)? + .set_alignment(revive_common::BYTE_LENGTH_BYTE as u32) + .expect("Alignment is valid"); + Ok(()) } diff --git a/crates/llvm-context/src/polkavm/evm/return.rs b/crates/llvm-context/src/polkavm/evm/return.rs index f152ae99..717ce811 100644 --- a/crates/llvm-context/src/polkavm/evm/return.rs +++ b/crates/llvm-context/src/polkavm/evm/return.rs @@ -1,9 +1,9 @@ //! 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::runtime::RuntimeFunction; use crate::polkavm::context::Context; +use crate::polkavm::evm::immutable::Store; use crate::polkavm::Dependency; /// Translates the `return` instruction. @@ -18,55 +18,11 @@ where match context.code_type() { None => anyhow::bail!("Return is not available if the contract part is undefined"), Some(CodeType::Deploy) => { - let immutable_data_size_pointer = context - .get_global(revive_runtime_api::immutable_data::GLOBAL_IMMUTABLE_DATA_SIZE)? - .value - .as_pointer_value(); - let immutable_data_size = context.build_load( - Pointer::new( - context.xlen_type(), - AddressSpace::Stack, - immutable_data_size_pointer, - ), - "immutable_data_size_load", - )?; - - let write_immutable_data_block = context.append_basic_block("write_immutables_block"); - let join_return_block = context.append_basic_block("join_return_block"); - let immutable_data_size_is_zero = context.builder().build_int_compare( - inkwell::IntPredicate::EQ, - context.xlen_type().const_zero(), - immutable_data_size.into_int_value(), - "immutable_data_size_is_zero", - )?; - context.build_conditional_branch( - immutable_data_size_is_zero, - join_return_block, - write_immutable_data_block, - )?; - - context.set_basic_block(write_immutable_data_block); - let immutable_data_pointer = context - .get_global(revive_runtime_api::immutable_data::GLOBAL_IMMUTABLE_DATA_POINTER)? - .value - .as_pointer_value(); - context.build_runtime_call( - revive_runtime_api::polkavm_imports::SET_IMMUTABLE_DATA, - &[ - context - .builder() - .build_ptr_to_int( - immutable_data_pointer, - context.xlen_type(), - "immutable_data_pointer_to_xlen", - )? - .into(), - immutable_data_size, - ], + context.build_call( + >::declaration(context), + Default::default(), + "store_immutable_data", ); - context.build_unconditional_branch(join_return_block); - - context.set_basic_block(join_return_block); } Some(CodeType::Runtime) => {} } @@ -100,11 +56,7 @@ pub fn stop(context: &mut Context) -> anyhow::Result<()> where D: Dependency + Clone, { - r#return( - context, - context.integer_const(crate::polkavm::XLEN, 0), - context.integer_const(crate::polkavm::XLEN, 0), - ) + r#return(context, context.word_const(0), context.word_const(0)) } /// Translates the `invalid` instruction. diff --git a/crates/llvm-context/src/polkavm/evm/storage.rs b/crates/llvm-context/src/polkavm/evm/storage.rs index e7080a88..cf409daf 100644 --- a/crates/llvm-context/src/polkavm/evm/storage.rs +++ b/crates/llvm-context/src/polkavm/evm/storage.rs @@ -1,8 +1,10 @@ //! Translates the storage operations. -use crate::polkavm::context::address_space::AddressSpace; +use crate::polkavm::context::runtime::RuntimeFunction; use crate::polkavm::context::Context; use crate::polkavm::Dependency; +use crate::PolkaVMLoadStorageWordFunction; +use crate::PolkaVMStoreStorageWordFunction; /// Translates the storage load. pub fn load<'ctx, D>( @@ -12,10 +14,12 @@ pub fn load<'ctx, D>( where D: Dependency + Clone, { - let mut slot_ptr = context.build_alloca_at_entry(context.word_type(), "slot_pointer"); - slot_ptr.address_space = AddressSpace::Storage; - context.builder().build_store(slot_ptr.value, position)?; - context.build_load(slot_ptr, "storage_load_value") + let name = >::NAME; + let declaration = >::declaration(context); + let arguments = [context.xlen_type().const_zero().into(), position.into()]; + Ok(context + .build_call(declaration, &arguments, "storage_load") + .unwrap_or_else(|| panic!("runtime function {name} should return a value"))) } /// Translates the storage store. @@ -27,10 +31,13 @@ pub fn store<'ctx, D>( where D: Dependency + Clone, { - let mut slot_ptr = context.build_alloca_at_entry(context.word_type(), "slot_pointer"); - slot_ptr.address_space = AddressSpace::Storage; - context.builder().build_store(slot_ptr.value, position)?; - context.build_store(slot_ptr, value)?; + let declaration = >::declaration(context); + let arguments = [ + context.xlen_type().const_zero().into(), + position.into(), + value.into(), + ]; + context.build_call(declaration, &arguments, "storage_store"); Ok(()) } @@ -42,10 +49,15 @@ pub fn transient_load<'ctx, D>( where D: Dependency + Clone, { - let mut slot_ptr = context.build_alloca_at_entry(context.word_type(), "slot_pointer"); - slot_ptr.address_space = AddressSpace::TransientStorage; - context.builder().build_store(slot_ptr.value, position)?; - context.build_load(slot_ptr, "transient_storage_load_value") + let name = >::NAME; + let declaration = >::declaration(context); + let arguments = [ + context.xlen_type().const_int(1, false).into(), + position.into(), + ]; + Ok(context + .build_call(declaration, &arguments, "storage_load") + .unwrap_or_else(|| panic!("runtime function {name} should return a value"))) } /// Translates the transient storage store. @@ -57,9 +69,12 @@ pub fn transient_store<'ctx, D>( where D: Dependency + Clone, { - let mut slot_ptr = context.build_alloca_at_entry(context.word_type(), "slot_pointer"); - slot_ptr.address_space = AddressSpace::TransientStorage; - context.builder().build_store(slot_ptr.value, position)?; - context.build_store(slot_ptr, value)?; + let declaration = >::declaration(context); + let arguments = [ + context.xlen_type().const_int(1, false).into(), + position.into(), + value.into(), + ]; + context.build_call(declaration, &arguments, "storage_store"); Ok(()) } diff --git a/crates/runner/Cargo.toml b/crates/runner/Cargo.toml index 06b403ab..277c0fa9 100644 --- a/crates/runner/Cargo.toml +++ b/crates/runner/Cargo.toml @@ -34,3 +34,4 @@ polkadot-sdk.features = [ revive-solidity = { workspace = true, optional = true } revive-differential = { workspace = true, optional = true } +revive-llvm-context = { workspace = true } diff --git a/crates/runner/src/lib.rs b/crates/runner/src/lib.rs index dd58a3dd..c759d59a 100644 --- a/crates/runner/src/lib.rs +++ b/crates/runner/src/lib.rs @@ -274,6 +274,7 @@ impl From for pallet_revive::Code { &contract, &source_code, solc_optimizer.unwrap_or(true), + revive_llvm_context::OptimizerSettings::cycles(), )) } Code::Path(path) => pallet_revive::Code::Upload(std::fs::read(path).unwrap()), diff --git a/crates/runtime-api/src/polkavm_imports.c b/crates/runtime-api/src/polkavm_imports.c index 170ac7f1..a97bc879 100644 --- a/crates/runtime-api/src/polkavm_imports.c +++ b/crates/runtime-api/src/polkavm_imports.c @@ -13,18 +13,18 @@ uint32_t __memory_size = 0; void * __sbrk_internal(uint32_t offset, uint32_t size) { if (offset >= MAX_MEMORY_SIZE || size > MAX_MEMORY_SIZE) { - return NULL; + POLKAVM_TRAP(); } uint32_t new_size = ALIGN(offset + size); if (new_size > MAX_MEMORY_SIZE) { - return NULL; + POLKAVM_TRAP(); } if (new_size > __memory_size) { __memory_size = new_size; } - return (void *)&__memory[__memory_size]; + return (void *)&__memory[offset]; } void * memset(void *b, int c, size_t len) { diff --git a/crates/solidity/src/test_utils.rs b/crates/solidity/src/test_utils.rs index f3a35f86..5bc71319 100644 --- a/crates/solidity/src/test_utils.rs +++ b/crates/solidity/src/test_utils.rs @@ -6,6 +6,7 @@ use std::path::PathBuf; use std::sync::Mutex; use once_cell::sync::Lazy; +use revive_llvm_context::OptimizerSettings; use crate::project::Project; use crate::solc::solc_compiler::SolcCompiler; @@ -31,6 +32,7 @@ struct CachedBlob { contract_name: String, solidity: String, solc_optimizer_enabled: bool, + opt: String, } /// Checks if the required executables are present in `${PATH}`. @@ -254,7 +256,12 @@ pub fn check_solidity_warning( /// Compile the blob of `contract_name` found in given `source_code`. /// The `solc` optimizer will be enabled pub fn compile_blob(contract_name: &str, source_code: &str) -> Vec { - compile_blob_with_options(contract_name, source_code, true) + compile_blob_with_options( + contract_name, + source_code, + true, + OptimizerSettings::cycles(), + ) } /// Compile the EVM bin-runtime of `contract_name` found in given `source_code`. @@ -283,6 +290,7 @@ fn compile_evm( contract_name: contract_name.to_owned(), solidity: source_code.to_owned(), solc_optimizer_enabled, + opt: String::new(), }; let cache = if runtime { @@ -322,11 +330,13 @@ pub fn compile_blob_with_options( contract_name: &str, source_code: &str, solc_optimizer_enabled: bool, + optimizer_settings: revive_llvm_context::OptimizerSettings, ) -> Vec { let id = CachedBlob { contract_name: contract_name.to_owned(), solidity: source_code.to_owned(), solc_optimizer_enabled, + opt: optimizer_settings.middle_end_as_string(), }; if let Some(blob) = PVM_BLOB_CACHE.lock().unwrap().get(&id) { @@ -338,7 +348,7 @@ pub fn compile_blob_with_options( [(file_name.into(), source_code.into())].into(), Default::default(), None, - revive_llvm_context::OptimizerSettings::cycles(), + optimizer_settings, solc_optimizer_enabled, ) .expect("source should compile") diff --git a/crates/solidity/src/yul/parser/statement/expression/function_call/mod.rs b/crates/solidity/src/yul/parser/statement/expression/function_call/mod.rs index c8f35fd9..b9aea789 100644 --- a/crates/solidity/src/yul/parser/statement/expression/function_call/mod.rs +++ b/crates/solidity/src/yul/parser/statement/expression/function_call/mod.rs @@ -672,7 +672,7 @@ impl FunctionCall { context, arguments[0].into_int_value(), arguments[1].into_int_value(), - vec![], + [], ) .map(|_| None) } @@ -682,10 +682,7 @@ impl FunctionCall { context, arguments[0].into_int_value(), arguments[1].into_int_value(), - arguments[2..] - .iter() - .map(|argument| argument.into_int_value()) - .collect(), + [arguments[2]], ) .map(|_| None) } @@ -695,10 +692,7 @@ impl FunctionCall { context, arguments[0].into_int_value(), arguments[1].into_int_value(), - arguments[2..] - .iter() - .map(|argument| argument.into_int_value()) - .collect(), + [arguments[2], arguments[3]], ) .map(|_| None) } @@ -708,10 +702,7 @@ impl FunctionCall { context, arguments[0].into_int_value(), arguments[1].into_int_value(), - arguments[2..] - .iter() - .map(|argument| argument.into_int_value()) - .collect(), + [arguments[2], arguments[3], arguments[4]], ) .map(|_| None) } @@ -721,10 +712,7 @@ impl FunctionCall { context, arguments[0].into_int_value(), arguments[1].into_int_value(), - arguments[2..] - .iter() - .map(|argument| argument.into_int_value()) - .collect(), + [arguments[2], arguments[3], arguments[4], arguments[5]], ) .map(|_| None) } diff --git a/crates/solidity/src/yul/parser/statement/function_definition.rs b/crates/solidity/src/yul/parser/statement/function_definition.rs index 1be78587..53f37591 100644 --- a/crates/solidity/src/yul/parser/statement/function_definition.rs +++ b/crates/solidity/src/yul/parser/statement/function_definition.rs @@ -216,7 +216,7 @@ where revive_llvm_context::PolkaVMFunction::set_attributes( context.llvm(), function.borrow().declaration(), - self.attributes.clone().into_iter().collect(), + &self.attributes.clone().into_iter().collect::>(), true, ); function diff --git a/crates/solidity/src/yul/parser/statement/object.rs b/crates/solidity/src/yul/parser/statement/object.rs index 4f101026..435a087d 100644 --- a/crates/solidity/src/yul/parser/statement/object.rs +++ b/crates/solidity/src/yul/parser/statement/object.rs @@ -185,7 +185,28 @@ where &mut self, context: &mut revive_llvm_context::PolkaVMContext, ) -> anyhow::Result<()> { - revive_llvm_context::PolkaVMImmutableDataLoadFunction.declare(context)?; + revive_llvm_context::PolkaVMLoadImmutableDataFunction.declare(context)?; + revive_llvm_context::PolkaVMStoreImmutableDataFunction.declare(context)?; + + revive_llvm_context::PolkaVMLoadHeapWordFunction.declare(context)?; + revive_llvm_context::PolkaVMStoreHeapWordFunction.declare(context)?; + revive_llvm_context::PolkaVMLoadStorageWordFunction.declare(context)?; + revive_llvm_context::PolkaVMStoreStorageWordFunction.declare(context)?; + + revive_llvm_context::PolkaVMWordToPointerFunction.declare(context)?; + revive_llvm_context::PolkaVMExitFunction.declare(context)?; + + revive_llvm_context::PolkaVMEventLogFunction::<0>.declare(context)?; + revive_llvm_context::PolkaVMEventLogFunction::<1>.declare(context)?; + revive_llvm_context::PolkaVMEventLogFunction::<2>.declare(context)?; + revive_llvm_context::PolkaVMEventLogFunction::<3>.declare(context)?; + revive_llvm_context::PolkaVMEventLogFunction::<4>.declare(context)?; + + revive_llvm_context::PolkaVMDivisionFunction.declare(context)?; + revive_llvm_context::PolkaVMSignedDivisionFunction.declare(context)?; + revive_llvm_context::PolkaVMRemainderFunction.declare(context)?; + revive_llvm_context::PolkaVMSignedRemainderFunction.declare(context)?; + let mut entry = revive_llvm_context::PolkaVMEntryFunction::default(); entry.declare(context)?; @@ -202,7 +223,6 @@ where revive_llvm_context::PolkaVMFunctionDeployCode, revive_llvm_context::PolkaVMFunctionRuntimeCode, revive_llvm_context::PolkaVMFunctionEntry, - revive_llvm_context::PolkaVMFunctionImmutableDataLoad, ] .into_iter() { @@ -215,6 +235,28 @@ where entry.into_llvm(context)?; + revive_llvm_context::PolkaVMLoadImmutableDataFunction.into_llvm(context)?; + revive_llvm_context::PolkaVMStoreImmutableDataFunction.into_llvm(context)?; + + revive_llvm_context::PolkaVMLoadHeapWordFunction.into_llvm(context)?; + revive_llvm_context::PolkaVMStoreHeapWordFunction.into_llvm(context)?; + revive_llvm_context::PolkaVMLoadStorageWordFunction.into_llvm(context)?; + revive_llvm_context::PolkaVMStoreStorageWordFunction.into_llvm(context)?; + + revive_llvm_context::PolkaVMWordToPointerFunction.into_llvm(context)?; + revive_llvm_context::PolkaVMExitFunction.into_llvm(context)?; + + revive_llvm_context::PolkaVMEventLogFunction::<0>.into_llvm(context)?; + revive_llvm_context::PolkaVMEventLogFunction::<1>.into_llvm(context)?; + revive_llvm_context::PolkaVMEventLogFunction::<2>.into_llvm(context)?; + revive_llvm_context::PolkaVMEventLogFunction::<3>.into_llvm(context)?; + revive_llvm_context::PolkaVMEventLogFunction::<4>.into_llvm(context)?; + + revive_llvm_context::PolkaVMDivisionFunction.into_llvm(context)?; + revive_llvm_context::PolkaVMSignedDivisionFunction.into_llvm(context)?; + revive_llvm_context::PolkaVMRemainderFunction.into_llvm(context)?; + revive_llvm_context::PolkaVMSignedRemainderFunction.into_llvm(context)?; + Ok(()) } @@ -230,7 +272,6 @@ where } if self.identifier.ends_with("_deployed") { - revive_llvm_context::PolkaVMImmutableDataLoadFunction.into_llvm(context)?; revive_llvm_context::PolkaVMRuntimeCodeFunction::new(self.code).into_llvm(context)?; } else { revive_llvm_context::PolkaVMDeployCodeFunction::new(self.code).into_llvm(context)?;