use std::num::NonZeroU32;
use inkwell::values::BasicValueEnum;
use revive_common::BYTE_LENGTH_BYTE;
use revive_common::BYTE_LENGTH_WORD;
use revive_common::BYTE_LENGTH_X64;
use crate::polkavm::context::attribute::MemoryEffect;
use crate::polkavm::context::runtime::RuntimeFunction;
use crate::polkavm::context::Context;
use crate::polkavm::WriteLLVM;
pub struct LoadWord;
impl RuntimeFunction for LoadWord {
const NAME: &'static str = "__revive_load_heap_word";
fn r#type<'ctx>(context: &Context<'ctx>) -> inkwell::types::FunctionType<'ctx> {
context
.word_type()
.fn_type(&[context.xlen_type().into()], false)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx>,
) -> anyhow::Result<Option<BasicValueEnum<'ctx>>> {
let offset = Self::paramater(context, 0).into_int_value();
let length = context
.xlen_type()
.const_int(BYTE_LENGTH_WORD as u64, false);
let pointer = context.build_heap_gep(offset, length)?;
let result = build_efficient_load_swap(context, pointer.value)?;
Ok(Some(result))
}
}
impl WriteLLVM for LoadWord {
fn declare(&mut self, context: &mut Context) -> anyhow::Result<()> {
<Self as RuntimeFunction>::declare(self, context)
}
fn into_llvm(self, context: &mut Context) -> anyhow::Result<()> {
<Self as RuntimeFunction>::emit(&self, context)
}
}
pub struct StoreWord;
impl RuntimeFunction for StoreWord {
const NAME: &'static str = "__revive_store_heap_word";
fn r#type<'ctx>(context: &Context<'ctx>) -> inkwell::types::FunctionType<'ctx> {
context.void_type().fn_type(
&[context.xlen_type().into(), context.word_type().into()],
false,
)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx>,
) -> anyhow::Result<Option<BasicValueEnum<'ctx>>> {
let offset = Self::paramater(context, 0).into_int_value();
let length = context
.xlen_type()
.const_int(BYTE_LENGTH_WORD as u64, false);
let pointer = context.build_heap_gep(offset, length)?;
let value = Self::paramater(context, 1).into_int_value();
build_efficient_store_swap(context, pointer.value, value)?;
Ok(None)
}
}
pub(crate) fn build_efficient_load_swap<'ctx>(
context: &Context<'ctx>,
pointer: inkwell::values::PointerValue<'ctx>,
) -> anyhow::Result<BasicValueEnum<'ctx>> {
if !context.is_newyork() {
let value = context
.builder()
.build_load(context.word_type(), pointer, "value")?;
context
.basic_block()
.get_last_instruction()
.expect("ICE: load instruction always exists")
.set_alignment(BYTE_LENGTH_BYTE as u32)
.expect("ICE: alignment is valid");
return context.build_byte_swap(value);
}
let i64_type = context
.llvm()
.custom_width_int_type(NonZeroU32::new(64).expect("const is non-zero"))
.expect("valid integer width");
let i8_type = context
.llvm()
.custom_width_int_type(NonZeroU32::new(8).expect("const is non-zero"))
.expect("valid integer width");
let word_type = context.word_type();
let bswap64 = inkwell::intrinsics::Intrinsic::find("llvm.bswap.i64")
.expect("ICE: llvm.bswap.i64 intrinsic exists");
let bswap64_function = bswap64
.get_declaration(context.module(), &[i64_type.into()])
.expect("ICE: bswap.i64 declaration");
let mut swapped_x64_values = Vec::with_capacity(4);
for index in 0..4 {
let gep_offset = context
.xlen_type()
.const_int(index * BYTE_LENGTH_X64 as u64, false);
let byte_pointer = unsafe {
context.builder().build_gep(
i8_type,
pointer,
&[gep_offset],
&format!("byte_pointer_{index}"),
)?
};
let x64_value = context
.builder()
.build_load(i64_type, byte_pointer, &format!("x64_value_{index}"))?
.into_int_value();
context
.basic_block()
.get_last_instruction()
.expect("ICE: load instruction always exists")
.set_alignment(BYTE_LENGTH_BYTE as u32)
.expect("ICE: alignment is valid");
let swapped_x64_value = context
.builder()
.build_call(
bswap64_function,
&[x64_value.into()],
&format!("swapped_x64_value_{index}"),
)?
.try_as_basic_value()
.unwrap_basic()
.into_int_value();
swapped_x64_values.push(swapped_x64_value);
}
let mut result =
context
.builder()
.build_int_z_extend(swapped_x64_values[3], word_type, "ext_0")?;
for (index, &x64_value) in swapped_x64_values.iter().rev().skip(1).enumerate() {
let extended = context.builder().build_int_z_extend(
x64_value,
word_type,
&format!("ext_{}", index + 1),
)?;
let shift_amount = word_type.const_int(64 * (index + 1) as u64, false);
let shifted = context.builder().build_left_shift(
extended,
shift_amount,
&format!("shift_{}", index + 1),
)?;
result = context
.builder()
.build_or(result, shifted, &format!("or_{}", index + 1))?;
}
Ok(result.into())
}
pub(crate) fn build_efficient_store_swap<'ctx>(
context: &Context<'ctx>,
pointer: inkwell::values::PointerValue<'ctx>,
value: inkwell::values::IntValue<'ctx>,
) -> anyhow::Result<()> {
if !context.is_newyork() {
let swapped_value = context.build_byte_swap(value.into())?;
context
.builder()
.build_store(pointer, swapped_value)?
.set_alignment(BYTE_LENGTH_BYTE as u32)
.expect("ICE: alignment is valid");
return Ok(());
}
let i64_type = context
.llvm()
.custom_width_int_type(NonZeroU32::new(64).expect("const is non-zero"))
.expect("valid integer width");
let i8_type = context
.llvm()
.custom_width_int_type(NonZeroU32::new(8).expect("const is non-zero"))
.expect("valid integer width");
let word_type = context.word_type();
let bswap64 = inkwell::intrinsics::Intrinsic::find("llvm.bswap.i64")
.expect("ICE: llvm.bswap.i64 intrinsic exists");
let bswap64_function = bswap64
.get_declaration(context.module(), &[i64_type.into()])
.expect("ICE: bswap.i64 declaration");
for index in 0..4 {
let shift_amount = word_type.const_int(64 * index as u64, false);
let shifted = context.builder().build_right_shift(
value,
shift_amount,
false,
&format!("shift_{index}"),
)?;
let x64_value =
context
.builder()
.build_int_truncate(shifted, i64_type, &format!("trunc_{index}"))?;
let swapped_x64_value = context
.builder()
.build_call(
bswap64_function,
&[x64_value.into()],
&format!("swap_x64_value_{index}"),
)?
.try_as_basic_value()
.unwrap_basic()
.into_int_value();
let store_byte_offset = (3 - index) * BYTE_LENGTH_X64;
let gep_offset = context
.xlen_type()
.const_int(store_byte_offset as u64, false);
let byte_pointer = unsafe {
context.builder().build_gep(
i8_type,
pointer,
&[gep_offset],
&format!("store_pointer_{index}"),
)?
};
let store_instruction = context
.builder()
.build_store(byte_pointer, swapped_x64_value)?;
store_instruction
.set_alignment(BYTE_LENGTH_BYTE as u32)
.expect("ICE: alignment is valid");
}
Ok(())
}
impl WriteLLVM for StoreWord {
fn declare(&mut self, context: &mut Context) -> anyhow::Result<()> {
<Self as RuntimeFunction>::declare(self, context)
}
fn into_llvm(self, context: &mut Context) -> anyhow::Result<()> {
<Self as RuntimeFunction>::emit(&self, context)
}
}
pub struct LoadWordNative;
impl RuntimeFunction for LoadWordNative {
const NAME: &'static str = "__revive_load_heap_word_native";
fn r#type<'ctx>(context: &Context<'ctx>) -> inkwell::types::FunctionType<'ctx> {
context
.word_type()
.fn_type(&[context.xlen_type().into()], false)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx>,
) -> anyhow::Result<Option<BasicValueEnum<'ctx>>> {
let offset = Self::paramater(context, 0).into_int_value();
let length = context
.xlen_type()
.const_int(BYTE_LENGTH_WORD as u64, false);
let pointer = context.build_heap_gep(offset, length)?;
let value = context
.builder()
.build_load(context.word_type(), pointer.value, "value")?;
context
.basic_block()
.get_last_instruction()
.expect("ICE: load instruction always exists")
.set_alignment(BYTE_LENGTH_BYTE as u32)
.expect("ICE: alignment is valid");
Ok(Some(value))
}
}
impl WriteLLVM for LoadWordNative {
fn declare(&mut self, context: &mut Context) -> anyhow::Result<()> {
<Self as RuntimeFunction>::declare(self, context)
}
fn into_llvm(self, context: &mut Context) -> anyhow::Result<()> {
<Self as RuntimeFunction>::emit(&self, context)
}
}
pub struct StoreWordNative;
impl RuntimeFunction for StoreWordNative {
const NAME: &'static str = "__revive_store_heap_word_native";
fn r#type<'ctx>(context: &Context<'ctx>) -> inkwell::types::FunctionType<'ctx> {
context.void_type().fn_type(
&[context.xlen_type().into(), context.word_type().into()],
false,
)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx>,
) -> anyhow::Result<Option<BasicValueEnum<'ctx>>> {
let offset = Self::paramater(context, 0).into_int_value();
let length = context
.xlen_type()
.const_int(BYTE_LENGTH_WORD as u64, false);
let pointer = context.build_heap_gep(offset, length)?;
let value = Self::paramater(context, 1);
context
.builder()
.build_store(pointer.value, value)?
.set_alignment(BYTE_LENGTH_BYTE as u32)
.expect("ICE: alignment is valid");
Ok(None)
}
}
impl WriteLLVM for StoreWordNative {
fn declare(&mut self, context: &mut Context) -> anyhow::Result<()> {
<Self as RuntimeFunction>::declare(self, context)
}
fn into_llvm(self, context: &mut Context) -> anyhow::Result<()> {
<Self as RuntimeFunction>::emit(&self, context)
}
}
pub struct Keccak256OneWord;
impl Keccak256OneWord {
pub const NAME: &'static str = "__revive_keccak256_one_word";
}
impl RuntimeFunction for Keccak256OneWord {
const NAME: &'static str = "__revive_keccak256_one_word";
const ATTRIBUTES: &'static [crate::polkavm::context::Attribute] = &[
crate::polkavm::context::Attribute::NoFree,
crate::polkavm::context::Attribute::NoRecurse,
crate::polkavm::context::Attribute::WillReturn,
crate::polkavm::context::Attribute::NoInline,
];
const MEMORY_EFFECT: MemoryEffect = MemoryEffect::Unrestricted;
fn r#type<'ctx>(context: &Context<'ctx>) -> inkwell::types::FunctionType<'ctx> {
context
.word_type()
.fn_type(&[context.word_type().into()], false)
}
fn emit_body<'ctx>(
&self,
context: &mut Context<'ctx>,
) -> anyhow::Result<Option<BasicValueEnum<'ctx>>> {
let word0 = Self::paramater(context, 0).into_int_value();
let zero_xlen = context.xlen_type().const_zero();
crate::polkavm::evm::memory::store_bswap_unchecked(context, zero_xlen, word0)?;
let length = context
.xlen_type()
.const_int(BYTE_LENGTH_WORD as u64, false);
let output_pointer = context.build_alloca_at_entry(context.word_type(), "output_pointer");
let input_pointer = context.build_heap_gep_unchecked(zero_xlen)?;
context.build_runtime_call(
revive_runtime_api::polkavm_imports::HASH_KECCAK_256,
&[
input_pointer.to_int(context).into(),
length.into(),
output_pointer.to_int(context).into(),
],
);
let result = context.build_byte_swap(context.build_load(output_pointer, "sha3_output")?)?;
Ok(Some(result))
}
}
impl WriteLLVM for Keccak256OneWord {
fn declare(&mut self, context: &mut Context) -> anyhow::Result<()> {
<Self as RuntimeFunction>::declare(self, context)
}
fn into_llvm(self, context: &mut Context) -> anyhow::Result<()> {
<Self as RuntimeFunction>::emit(&self, context)
}
}
pub struct Keccak256TwoWords;
impl Keccak256TwoWords {
pub const NAME: &'static str = "__revive_keccak256_two_words";
}
impl RuntimeFunction for Keccak256TwoWords {
const NAME: &'static str = "__revive_keccak256_two_words";
const ATTRIBUTES: &'static [crate::polkavm::context::Attribute] = &[
crate::polkavm::context::Attribute::NoFree,
crate::polkavm::context::Attribute::NoRecurse,
crate::polkavm::context::Attribute::WillReturn,
crate::polkavm::context::Attribute::NoInline,
];
const MEMORY_EFFECT: MemoryEffect = MemoryEffect::Unrestricted;
fn r#type<'ctx>(context: &Context<'ctx>) -> 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>,
) -> anyhow::Result<Option<BasicValueEnum<'ctx>>> {
let word0 = Self::paramater(context, 0).into_int_value();
let word1 = Self::paramater(context, 1).into_int_value();
let zero_xlen = context.xlen_type().const_zero();
let word_xlen = context
.xlen_type()
.const_int(BYTE_LENGTH_WORD as u64, false);
crate::polkavm::evm::memory::store_bswap_unchecked(context, zero_xlen, word0)?;
crate::polkavm::evm::memory::store_bswap_unchecked(context, word_xlen, word1)?;
let length = context
.xlen_type()
.const_int(2 * BYTE_LENGTH_WORD as u64, false);
let output_pointer = context.build_alloca_at_entry(context.word_type(), "output_pointer");
let input_pointer = context.build_heap_gep_unchecked(zero_xlen)?;
context.build_runtime_call(
revive_runtime_api::polkavm_imports::HASH_KECCAK_256,
&[
input_pointer.to_int(context).into(),
length.into(),
output_pointer.to_int(context).into(),
],
);
let result = context.build_byte_swap(context.build_load(output_pointer, "sha3_output")?)?;
Ok(Some(result))
}
}
impl WriteLLVM for Keccak256TwoWords {
fn declare(&mut self, context: &mut Context) -> anyhow::Result<()> {
<Self as RuntimeFunction>::declare(self, context)
}
fn into_llvm(self, context: &mut Context) -> anyhow::Result<()> {
<Self as RuntimeFunction>::emit(&self, context)
}
}