implement division and remainder operations

Signed-off-by: xermicus <cyrill@parity.io>
This commit is contained in:
xermicus
2024-05-08 23:21:16 +02:00
parent 864e40901f
commit 6af889c1ff
6 changed files with 310 additions and 173 deletions
@@ -16,15 +16,6 @@ pub struct LLVMRuntime<'ctx> {
/// The LLVM exception throwing function.
pub cxa_throw: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub div: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub sdiv: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub r#mod: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub smod: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
pub shl: FunctionDeclaration<'ctx>,
/// The corresponding LLVM runtime function.
@@ -82,18 +73,6 @@ impl<'ctx> LLVMRuntime<'ctx> {
/// The LLVM exception throwing function name.
pub const FUNCTION_CXA_THROW: &'static str = "__cxa_throw";
/// The corresponding runtime function name.
pub const FUNCTION_DIV: &'static str = "__div";
/// The corresponding runtime function name.
pub const FUNCTION_SDIV: &'static str = "__sdiv";
/// The corresponding runtime function name.
pub const FUNCTION_MOD: &'static str = "__mod";
/// The corresponding runtime function name.
pub const FUNCTION_SMOD: &'static str = "__smod";
/// The corresponding runtime function name.
pub const FUNCTION_SHL: &'static str = "__shl";
@@ -184,82 +163,6 @@ impl<'ctx> LLVMRuntime<'ctx> {
);
Function::set_cxa_throw_attributes(llvm, cxa_throw);
let div = Self::declare(
module,
Self::FUNCTION_DIV,
llvm.custom_width_int_type(revive_common::BIT_LENGTH_WORD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(revive_common::BIT_LENGTH_WORD as u32)
.as_basic_type_enum()
.into();
2
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, div, optimizer);
Function::set_pure_function_attributes(llvm, div);
let r#mod = Self::declare(
module,
Self::FUNCTION_MOD,
llvm.custom_width_int_type(revive_common::BIT_LENGTH_WORD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(revive_common::BIT_LENGTH_WORD as u32)
.as_basic_type_enum()
.into();
2
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, r#mod, optimizer);
Function::set_pure_function_attributes(llvm, r#mod);
let sdiv = Self::declare(
module,
Self::FUNCTION_SDIV,
llvm.custom_width_int_type(revive_common::BIT_LENGTH_WORD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(revive_common::BIT_LENGTH_WORD as u32)
.as_basic_type_enum()
.into();
2
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, sdiv, optimizer);
Function::set_pure_function_attributes(llvm, sdiv);
let smod = Self::declare(
module,
Self::FUNCTION_SMOD,
llvm.custom_width_int_type(revive_common::BIT_LENGTH_WORD as u32)
.fn_type(
vec![
llvm.custom_width_int_type(revive_common::BIT_LENGTH_WORD as u32)
.as_basic_type_enum()
.into();
2
]
.as_slice(),
false,
),
Some(inkwell::module::Linkage::External),
);
Function::set_default_attributes(llvm, smod, optimizer);
Function::set_pure_function_attributes(llvm, smod);
let shl = Self::declare(
module,
Self::FUNCTION_SHL,
@@ -554,11 +457,6 @@ impl<'ctx> LLVMRuntime<'ctx> {
personality,
cxa_throw,
div,
sdiv,
r#mod,
smod,
shl,
shr,
sar,
+132 -34
View File
@@ -59,10 +59,11 @@ pub fn division<'ctx, D>(
where
D: Dependency + Clone,
{
Ok(context
.builder()
.build_int_unsigned_div(operand_1, operand_2, "udiv")?
.into())
WrappedDivision::new(context, operand_2)?.with(|| {
Ok(context
.builder()
.build_int_unsigned_div(operand_1, operand_2, "DIV")?)
})
}
/// Translates the arithmetic remainder.
@@ -74,16 +75,11 @@ pub fn remainder<'ctx, D>(
where
D: Dependency + Clone,
{
Ok(context
.build_call(
context.llvm_runtime().r#mod,
&[
operand_1.as_basic_value_enum(),
operand_2.as_basic_value_enum(),
],
"add_mod_call",
)
.expect("Always exists"))
WrappedDivision::new(context, operand_2)?.with(|| {
Ok(context
.builder()
.build_int_unsigned_rem(operand_1, operand_2, "MOD")?)
})
}
/// Translates the signed arithmetic division.
@@ -98,16 +94,59 @@ pub fn division_signed<'ctx, D>(
where
D: Dependency + Clone,
{
Ok(context
.build_call(
context.llvm_runtime().sdiv,
&[
operand_1.as_basic_value_enum(),
operand_2.as_basic_value_enum(),
],
"add_mod_call",
)
.expect("Always exists"))
WrappedDivision::new(context, operand_2)?.with(|| {
let block_no_overflow = context.append_basic_block("no_overflow");
let block_operand_1_overflow = context.append_basic_block("operand_1_overflow");
let block_select_quotient = context.append_basic_block("block_select_quotient");
let max_uint = context.builder().build_int_z_extend(
context
.integer_type(revive_common::BIT_LENGTH_WORD - 1)
.const_all_ones(),
context.word_type(),
"constant_zext_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_operand_1_overflow,
block_no_overflow,
)?;
context.set_basic_block(block_operand_1_overflow);
let is_operand_2_overflow = context.builder().build_int_compare(
inkwell::IntPredicate::EQ,
operand_2,
context.word_type().const_all_ones(),
"is_operand_2_overflow",
)?;
context.build_conditional_branch(
is_operand_2_overflow,
block_select_quotient,
block_no_overflow,
)?;
context.set_basic_block(block_no_overflow);
let quotient = context
.builder()
.build_int_signed_div(operand_1, operand_2, "SDIV")?;
context.build_unconditional_branch(block_select_quotient);
context.set_basic_block(block_select_quotient);
let phi_value = context
.builder()
.build_phi(context.word_type(), "phi_quotient")?;
phi_value.add_incoming(&[
(&quotient.as_basic_value_enum(), block_no_overflow),
(&operand_1, block_operand_1_overflow),
]);
Ok(phi_value.as_basic_value().into_int_value())
})
}
/// Translates the signed arithmetic remainder.
@@ -119,14 +158,73 @@ pub fn remainder_signed<'ctx, D>(
where
D: Dependency + Clone,
{
Ok(context
.build_call(
context.llvm_runtime().smod,
&[
operand_1.as_basic_value_enum(),
operand_2.as_basic_value_enum(),
],
"add_mod_call",
)
.expect("Always exists"))
WrappedDivision::new(context, operand_2)?.with(|| {
Ok(context
.builder()
.build_int_signed_rem(operand_1, operand_2, "SMOD")?)
})
}
/// Helper to wrap division operations so that zero will be returned
/// if the denominator is zero (see also Ethereum YP Appendix H.2).
struct WrappedDivision<'a, 'ctx, D: Dependency + Clone> {
context: &'a Context<'ctx, D>,
block_origin: inkwell::basic_block::BasicBlock<'ctx>,
block_calculate: inkwell::basic_block::BasicBlock<'ctx>,
block_select: inkwell::basic_block::BasicBlock<'ctx>,
}
impl<'a, 'ctx, D: Dependency + Clone> WrappedDivision<'a, 'ctx, D> {
/// Create a new wrapped division (inserts a switch on the denominator).
fn new(
context: &'a Context<'ctx, D>,
denominator: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<Self> {
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");
context.builder().build_switch(
denominator,
block_calculate,
&[(context.word_const(0), block_select)],
)?;
Ok(Self {
context,
block_origin: context.basic_block(),
block_calculate,
block_select,
})
}
/// Insert code to calculate the operation.
///
/// The closure is expected to calculate and return the quotient.
///
/// The returned value is either the calculated quotient or zero, selected at runtime.
fn with<T, F>(self, f: F) -> anyhow::Result<inkwell::values::BasicValueEnum<'ctx>>
where
F: FnOnce() -> anyhow::Result<T>,
T: inkwell::values::IntMathValue<'ctx>,
{
self.context.set_basic_block(self.block_calculate);
let calculated_value = f()?.as_basic_value_enum();
let calculated_value_incoming_block = self.context.basic_block();
self.context.build_unconditional_branch(self.block_select);
self.context.set_basic_block(self.block_select);
let phi_value = self
.context
.builder()
.build_phi(self.context.word_type(), "phi_result")?;
phi_value.add_incoming(&[
(&self.context.word_const(0), self.block_origin),
(&calculated_value, calculated_value_incoming_block),
]);
Ok(phi_value.as_basic_value())
}
}