mirror of
https://github.com/pezkuwichain/revive.git
synced 2026-06-14 21:31:05 +00:00
a07968205b
- 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.
270 lines
8.4 KiB
Rust
270 lines
8.4 KiB
Rust
//! 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<D> RuntimeFunction<D> 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<Option<inkwell::values::BasicValueEnum<'ctx>>> {
|
|
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<D> WriteLLVM<D> for Division
|
|
where
|
|
D: Dependency + Clone,
|
|
{
|
|
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
|
|
<Self as RuntimeFunction<_>>::declare(self, context)
|
|
}
|
|
|
|
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
|
|
<Self as RuntimeFunction<_>>::emit(&self, context)
|
|
}
|
|
}
|
|
|
|
/// Implements the signed division operator according to the EVM specification.
|
|
pub struct SignedDivision;
|
|
|
|
impl<D> RuntimeFunction<D> 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<Option<inkwell::values::BasicValueEnum<'ctx>>> {
|
|
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<D> WriteLLVM<D> for SignedDivision
|
|
where
|
|
D: Dependency + Clone,
|
|
{
|
|
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
|
|
<Self as RuntimeFunction<_>>::declare(self, context)
|
|
}
|
|
|
|
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
|
|
<Self as RuntimeFunction<_>>::emit(&self, context)
|
|
}
|
|
}
|
|
|
|
/// Implements the remainder operator according to the EVM specification.
|
|
pub struct Remainder;
|
|
|
|
impl<D> RuntimeFunction<D> 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<Option<inkwell::values::BasicValueEnum<'ctx>>> {
|
|
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<D> WriteLLVM<D> for Remainder
|
|
where
|
|
D: Dependency + Clone,
|
|
{
|
|
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
|
|
<Self as RuntimeFunction<_>>::declare(self, context)
|
|
}
|
|
|
|
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
|
|
<Self as RuntimeFunction<_>>::emit(&self, context)
|
|
}
|
|
}
|
|
|
|
/// Implements the signed remainder operator according to the EVM specification.
|
|
pub struct SignedRemainder;
|
|
|
|
impl<D> RuntimeFunction<D> 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<Option<inkwell::values::BasicValueEnum<'ctx>>> {
|
|
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<D> WriteLLVM<D> for SignedRemainder
|
|
where
|
|
D: Dependency + Clone,
|
|
{
|
|
fn declare(&mut self, context: &mut Context<D>) -> anyhow::Result<()> {
|
|
<Self as RuntimeFunction<_>>::declare(self, context)
|
|
}
|
|
|
|
fn into_llvm(self, context: &mut Context<D>) -> anyhow::Result<()> {
|
|
<Self as RuntimeFunction<_>>::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<inkwell::values::BasicValueEnum<'ctx>>
|
|
where
|
|
D: Dependency + Clone,
|
|
F: FnOnce() -> anyhow::Result<T>,
|
|
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())
|
|
}
|