Skip to content

Commit

Permalink
llvm-context: modularize compiler builtin functions (#234)
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
xermicus authored Feb 25, 2025
1 parent 7ffe64e commit a079682
Show file tree
Hide file tree
Showing 32 changed files with 1,444 additions and 655 deletions.
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/integration/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
16 changes: 8 additions & 8 deletions crates/integration/codesize.json
Original file line number Diff line number Diff line change
@@ -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
}
62 changes: 52 additions & 10 deletions crates/integration/src/cases.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down Expand Up @@ -250,19 +251,27 @@ sol!(
case!("Storage.sol", Storage, transientCall, storage_transient, value: U256);

impl Contract {
fn build(calldata: Vec<u8>, name: &'static str, code: &str) -> Self {
pub fn build(calldata: Vec<u8>, name: &'static str, code: &str) -> Self {
Self {
name,
evm_runtime: compile_evm_bin_runtime(name, code),
pvm_runtime: compile_blob(name, code),
calldata,
}
}

pub fn build_size_opt(calldata: Vec<u8>, 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};
Expand Down Expand Up @@ -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)
Expand Down
1 change: 0 additions & 1 deletion crates/linker/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ pub fn link<T: AsRef<[u8]>>(input: T) -> anyhow::Result<Vec<u8>> {

let ld_args = [
"ld.lld",
"--lto=full",
"--error-limit=0",
"--relocatable",
"--emit-relocs",
Expand Down
16 changes: 14 additions & 2 deletions crates/llvm-context/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ impl<'ctx> LLVMRuntime<'ctx> {
llvm,
sha3,
//vec![Attribute::ArgMemOnly, Attribute::ReadOnly],
vec![],
&[],
false,
);

Expand Down
57 changes: 18 additions & 39 deletions crates/llvm-context/src/polkavm/context/function/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -110,30 +109,21 @@ impl<'ctx> Function<'ctx> {
pub fn set_attributes(
llvm: &'ctx inkwell::context::Context,
declaration: Declaration<'ctx>,
attributes: Vec<Attribute>,
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(
Expand All @@ -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),
),
}
}
Expand Down Expand Up @@ -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.
Expand All @@ -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);
}
}

Expand All @@ -220,7 +199,7 @@ impl<'ctx> Function<'ctx> {
Self::set_attributes(
llvm,
declaration,
vec![
&[
Attribute::MustProgress,
Attribute::NoUnwind,
Attribute::WillReturn,
Expand Down
Loading

0 comments on commit a079682

Please sign in to comment.