mirror of
https://github.com/pezkuwichain/revive.git
synced 2026-06-17 11:11:10 +00:00
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.
This commit is contained in:
@@ -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<D>
|
||||
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<D>) -> 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<D>) -> 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<Option<inkwell::values::BasicValueEnum<'ctx>>>;
|
||||
|
||||
/// Emit the function return instructions.
|
||||
fn emit_epilogue<'ctx>(
|
||||
&self,
|
||||
context: &mut Context<'ctx, D>,
|
||||
return_value: Option<inkwell::values::BasicValueEnum<'ctx>>,
|
||||
) {
|
||||
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}"))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user