Skip to content

Commit a079682

Browse files
authored
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.
1 parent 7ffe64e commit a079682

File tree

32 files changed

+1444
-655
lines changed

32 files changed

+1444
-655
lines changed

CHANGELOG.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,18 @@
22

33
## Unreleased
44

5+
## v0.1.0-dev.12
6+
57
This is a development pre-release.
68

7-
Supported `polkadot-sdk` rev: `274a781e8ca1a9432c7ec87593bd93214abbff50`
9+
Supported `polkadot-sdk` rev: `21f6f0705e53c15aa2b8a5706b208200447774a9`
10+
11+
### Added
12+
13+
### Changed
14+
- Improved code size: Large contracts compile to smaller code blobs using with size optimization.
15+
16+
### Fixed
817

918
## v0.1.0-dev.11
1019

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/integration/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ serde_json = { workspace = true }
1515

1616
revive-solidity = { workspace = true }
1717
revive-runner = { workspace = true }
18+
revive-llvm-context = { workspace = true }
1819

1920
[dev-dependencies]
2021
sha1 = { workspace = true }

crates/integration/codesize.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
2-
"Baseline": 1237,
3-
"Computation": 3119,
4-
"DivisionArithmetics": 16561,
5-
"ERC20": 23966,
6-
"Events": 2102,
7-
"FibonacciIterative": 2521,
8-
"Flipper": 2745,
9-
"SHA1": 17004
2+
"Baseline": 1443,
3+
"Computation": 2788,
4+
"DivisionArithmetics": 9748,
5+
"ERC20": 19203,
6+
"Events": 2201,
7+
"FibonacciIterative": 2041,
8+
"Flipper": 2632,
9+
"SHA1": 8958
1010
}

crates/integration/src/cases.rs

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use alloy_primitives::{Address, Bytes, I256, U256};
22
use alloy_sol_types::{sol, SolCall, SolConstructor};
33

4+
use revive_llvm_context::OptimizerSettings;
45
use revive_solidity::test_utils::*;
56

67
#[derive(Clone)]
@@ -250,19 +251,27 @@ sol!(
250251
case!("Storage.sol", Storage, transientCall, storage_transient, value: U256);
251252

252253
impl Contract {
253-
fn build(calldata: Vec<u8>, name: &'static str, code: &str) -> Self {
254+
pub fn build(calldata: Vec<u8>, name: &'static str, code: &str) -> Self {
254255
Self {
255256
name,
256257
evm_runtime: compile_evm_bin_runtime(name, code),
257258
pvm_runtime: compile_blob(name, code),
258259
calldata,
259260
}
260261
}
262+
263+
pub fn build_size_opt(calldata: Vec<u8>, name: &'static str, code: &str) -> Self {
264+
Self {
265+
name,
266+
evm_runtime: compile_evm_bin_runtime(name, code),
267+
pvm_runtime: compile_blob_with_options(name, code, true, OptimizerSettings::size()),
268+
calldata,
269+
}
270+
}
261271
}
262272

263273
#[cfg(test)]
264274
mod tests {
265-
use alloy_primitives::{Bytes, U256};
266275
use rayon::iter::{IntoParallelIterator, ParallelIterator};
267276
use serde::{de::Deserialize, Serialize};
268277
use std::{collections::BTreeMap, fs::File};
@@ -302,14 +311,47 @@ mod tests {
302311
};
303312

304313
[
305-
Contract::baseline as fn() -> Contract,
306-
Contract::flipper as fn() -> Contract,
307-
(|| Contract::odd_product(0)) as fn() -> Contract,
308-
(|| Contract::fib_iterative(U256::ZERO)) as fn() -> Contract,
309-
Contract::erc20 as fn() -> Contract,
310-
(|| Contract::sha1(Bytes::new())) as fn() -> Contract,
311-
(|| Contract::division_arithmetics_div(U256::ZERO, U256::ZERO)) as fn() -> Contract,
312-
(|| Contract::event(U256::ZERO)) as fn() -> Contract,
314+
(|| {
315+
Contract::build_size_opt(
316+
vec![],
317+
"Baseline",
318+
include_str!("../contracts/Baseline.sol"),
319+
)
320+
}) as _,
321+
(|| {
322+
Contract::build_size_opt(
323+
vec![],
324+
"Flipper",
325+
include_str!("../contracts/flipper.sol"),
326+
)
327+
}) as _,
328+
(|| {
329+
Contract::build_size_opt(
330+
vec![],
331+
"Computation",
332+
include_str!("../contracts/Computation.sol"),
333+
)
334+
}) as _,
335+
(|| {
336+
Contract::build_size_opt(
337+
vec![],
338+
"FibonacciIterative",
339+
include_str!("../contracts/Fibonacci.sol"),
340+
)
341+
}) as _,
342+
(|| Contract::build_size_opt(vec![], "ERC20", include_str!("../contracts/ERC20.sol")))
343+
as _,
344+
(|| Contract::build_size_opt(vec![], "SHA1", include_str!("../contracts/SHA1.sol")))
345+
as _,
346+
(|| {
347+
Contract::build_size_opt(
348+
vec![],
349+
"DivisionArithmetics",
350+
include_str!("../contracts/DivisionArithmetics.sol"),
351+
)
352+
}) as _,
353+
(|| Contract::build_size_opt(vec![], "Events", include_str!("../contracts/Events.sol")))
354+
as _,
313355
]
314356
.into_par_iter()
315357
.map(extract_code_size)

crates/linker/src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ pub fn link<T: AsRef<[u8]>>(input: T) -> anyhow::Result<Vec<u8>> {
5252

5353
let ld_args = [
5454
"ld.lld",
55-
"--lto=full",
5655
"--error-limit=0",
5756
"--relocatable",
5857
"--emit-relocs",

crates/llvm-context/src/lib.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,25 @@ pub use self::polkavm::context::function::declaration::Declaration as PolkaVMFun
2121
pub use self::polkavm::context::function::intrinsics::Intrinsics as PolkaVMIntrinsicFunction;
2222
pub use self::polkavm::context::function::llvm_runtime::LLVMRuntime as PolkaVMLLVMRuntime;
2323
pub use self::polkavm::context::function::r#return::Return as PolkaVMFunctionReturn;
24+
pub use self::polkavm::context::function::runtime::arithmetics::Division as PolkaVMDivisionFunction;
25+
pub use self::polkavm::context::function::runtime::arithmetics::Remainder as PolkaVMRemainderFunction;
26+
pub use self::polkavm::context::function::runtime::arithmetics::SignedDivision as PolkaVMSignedDivisionFunction;
27+
pub use self::polkavm::context::function::runtime::arithmetics::SignedRemainder as PolkaVMSignedRemainderFunction;
2428
pub use self::polkavm::context::function::runtime::deploy_code::DeployCode as PolkaVMDeployCodeFunction;
2529
pub use self::polkavm::context::function::runtime::entry::Entry as PolkaVMEntryFunction;
26-
pub use self::polkavm::context::function::runtime::immutable_data_load::ImmutableDataLoad as PolkaVMImmutableDataLoadFunction;
30+
pub use self::polkavm::context::function::runtime::revive::Exit as PolkaVMExitFunction;
31+
pub use self::polkavm::context::function::runtime::revive::WordToPointer as PolkaVMWordToPointerFunction;
2732
pub use self::polkavm::context::function::runtime::runtime_code::RuntimeCode as PolkaVMRuntimeCodeFunction;
2833
pub use self::polkavm::context::function::runtime::FUNCTION_DEPLOY_CODE as PolkaVMFunctionDeployCode;
2934
pub use self::polkavm::context::function::runtime::FUNCTION_ENTRY as PolkaVMFunctionEntry;
30-
pub use self::polkavm::context::function::runtime::FUNCTION_LOAD_IMMUTABLE_DATA as PolkaVMFunctionImmutableDataLoad;
3135
pub use self::polkavm::context::function::runtime::FUNCTION_RUNTIME_CODE as PolkaVMFunctionRuntimeCode;
3236
pub use self::polkavm::context::function::yul_data::YulData as PolkaVMFunctionYulData;
3337
pub use self::polkavm::context::function::Function as PolkaVMFunction;
3438
pub use self::polkavm::context::global::Global as PolkaVMGlobal;
39+
pub use self::polkavm::context::pointer::heap::LoadWord as PolkaVMLoadHeapWordFunction;
40+
pub use self::polkavm::context::pointer::heap::StoreWord as PolkaVMStoreHeapWordFunction;
41+
pub use self::polkavm::context::pointer::storage::LoadWord as PolkaVMLoadStorageWordFunction;
42+
pub use self::polkavm::context::pointer::storage::StoreWord as PolkaVMStoreStorageWordFunction;
3543
pub use self::polkavm::context::pointer::Pointer as PolkaVMPointer;
3644
pub use self::polkavm::context::r#loop::Loop as PolkaVMLoop;
3745
pub use self::polkavm::context::solidity_data::SolidityData as PolkaVMContextSolidityData;
@@ -47,8 +55,12 @@ pub use self::polkavm::evm::create as polkavm_evm_create;
4755
pub use self::polkavm::evm::crypto as polkavm_evm_crypto;
4856
pub use self::polkavm::evm::ether_gas as polkavm_evm_ether_gas;
4957
pub use self::polkavm::evm::event as polkavm_evm_event;
58+
pub use self::polkavm::evm::event::EventLog as PolkaVMEventLogFunction;
5059
pub use self::polkavm::evm::ext_code as polkavm_evm_ext_code;
5160
pub use self::polkavm::evm::immutable as polkavm_evm_immutable;
61+
pub use self::polkavm::evm::immutable::Load as PolkaVMLoadImmutableDataFunction;
62+
pub use self::polkavm::evm::immutable::Store as PolkaVMStoreImmutableDataFunction;
63+
5264
pub use self::polkavm::evm::math as polkavm_evm_math;
5365
pub use self::polkavm::evm::memory as polkavm_evm_memory;
5466
pub use self::polkavm::evm::r#return as polkavm_evm_return;

crates/llvm-context/src/polkavm/context/function/llvm_runtime.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ impl<'ctx> LLVMRuntime<'ctx> {
9191
llvm,
9292
sha3,
9393
//vec![Attribute::ArgMemOnly, Attribute::ReadOnly],
94-
vec![],
94+
&[],
9595
false,
9696
);
9797

crates/llvm-context/src/polkavm/context/function/mod.rs

Lines changed: 18 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,7 @@ impl<'ctx> Function<'ctx> {
8181
|| (name.starts_with("__")
8282
&& name != self::runtime::FUNCTION_ENTRY
8383
&& name != self::runtime::FUNCTION_DEPLOY_CODE
84-
&& name != self::runtime::FUNCTION_RUNTIME_CODE
85-
&& name != self::runtime::FUNCTION_LOAD_IMMUTABLE_DATA)
84+
&& name != self::runtime::FUNCTION_RUNTIME_CODE)
8685
}
8786

8887
/// Returns the LLVM function declaration.
@@ -110,30 +109,21 @@ impl<'ctx> Function<'ctx> {
110109
pub fn set_attributes(
111110
llvm: &'ctx inkwell::context::Context,
112111
declaration: Declaration<'ctx>,
113-
attributes: Vec<Attribute>,
112+
attributes: &[Attribute],
114113
force: bool,
115114
) {
116-
for attribute_kind in attributes.into_iter() {
115+
for attribute_kind in attributes {
117116
match attribute_kind {
118117
Attribute::Memory => unimplemented!("`memory` attributes are not implemented"),
119118
attribute_kind @ Attribute::AlwaysInline if force => {
120-
let is_optimize_none_set = declaration
121-
.value
122-
.get_enum_attribute(
123-
inkwell::attributes::AttributeLoc::Function,
124-
Attribute::OptimizeNone as u32,
125-
)
126-
.is_some();
127-
if !is_optimize_none_set {
128-
declaration.value.remove_enum_attribute(
129-
inkwell::attributes::AttributeLoc::Function,
130-
Attribute::NoInline as u32,
131-
);
132-
declaration.value.add_attribute(
133-
inkwell::attributes::AttributeLoc::Function,
134-
llvm.create_enum_attribute(attribute_kind as u32, 0),
135-
);
136-
}
119+
declaration.value.remove_enum_attribute(
120+
inkwell::attributes::AttributeLoc::Function,
121+
Attribute::NoInline as u32,
122+
);
123+
declaration.value.add_attribute(
124+
inkwell::attributes::AttributeLoc::Function,
125+
llvm.create_enum_attribute(*attribute_kind as u32, 0),
126+
);
137127
}
138128
attribute_kind @ Attribute::NoInline if force => {
139129
declaration.value.remove_enum_attribute(
@@ -142,12 +132,12 @@ impl<'ctx> Function<'ctx> {
142132
);
143133
declaration.value.add_attribute(
144134
inkwell::attributes::AttributeLoc::Function,
145-
llvm.create_enum_attribute(attribute_kind as u32, 0),
135+
llvm.create_enum_attribute(*attribute_kind as u32, 0),
146136
);
147137
}
148138
attribute_kind => declaration.value.add_attribute(
149139
inkwell::attributes::AttributeLoc::Function,
150-
llvm.create_enum_attribute(attribute_kind as u32, 0),
140+
llvm.create_enum_attribute(*attribute_kind as u32, 0),
151141
),
152142
}
153143
}
@@ -178,27 +168,16 @@ impl<'ctx> Function<'ctx> {
178168
declaration: Declaration<'ctx>,
179169
optimizer: &Optimizer,
180170
) {
181-
if optimizer.settings().level_middle_end == inkwell::OptimizationLevel::None {
182-
Self::remove_attributes(
183-
declaration,
184-
&[Attribute::OptimizeForSize, Attribute::AlwaysInline],
185-
);
186-
Self::set_attributes(
187-
llvm,
188-
declaration,
189-
vec![Attribute::OptimizeNone, Attribute::NoInline],
190-
false,
191-
);
192-
} else if optimizer.settings().level_middle_end_size == SizeLevel::Z {
171+
if optimizer.settings().level_middle_end_size == SizeLevel::Z {
193172
Self::set_attributes(
194173
llvm,
195174
declaration,
196-
vec![Attribute::OptimizeForSize, Attribute::MinSize],
175+
&[Attribute::OptimizeForSize, Attribute::MinSize],
197176
false,
198177
);
199178
}
200179

201-
Self::set_attributes(llvm, declaration, vec![Attribute::NoFree], false);
180+
Self::set_attributes(llvm, declaration, &[Attribute::NoFree], false);
202181
}
203182

204183
/// Sets the front-end runtime attributes.
@@ -208,7 +187,7 @@ impl<'ctx> Function<'ctx> {
208187
optimizer: &Optimizer,
209188
) {
210189
if optimizer.settings().level_middle_end_size == SizeLevel::Z {
211-
Self::set_attributes(llvm, declaration, vec![Attribute::NoInline], false);
190+
Self::set_attributes(llvm, declaration, &[Attribute::NoInline], false);
212191
}
213192
}
214193

@@ -220,7 +199,7 @@ impl<'ctx> Function<'ctx> {
220199
Self::set_attributes(
221200
llvm,
222201
declaration,
223-
vec![
202+
&[
224203
Attribute::MustProgress,
225204
Attribute::NoUnwind,
226205
Attribute::WillReturn,

0 commit comments

Comments
 (0)